Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,19 @@ make calls to retrieve account/budget information. We recommend using the
# now you can do all the normal api calls
# ex:
mint.get_transaction_data()

# Update a transaction
# Note that the list of tag_ids replaces the existing tags on the transaction, rather than adding to them.
updated_transaction = mint.update_transaction_data(
transaction_id="15123456_2345678910_0",
description="McDonald's",
notes="Lunch",
category_id="15123456_1234",
tag_ids=["15123456_1234567"],
transaction_type="CashAndCreditTransaction",
)


```

---
Expand Down
117 changes: 113 additions & 4 deletions mintapi/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import logging
from abc import ABCMeta, abstractmethod
from datetime import datetime
from typing import List, Optional
from typing import List, Optional, Dict
from urllib.parse import parse_qs, urlparse

import pandas as pd
from mintapi.constants import MINT_CREDIT_URL, MINT_ROOT_URL
from mintapi.filters import DateFilter, SearchFilterBuilder
from mintapi.transactions import TransactionRequest
from mintapi.transactions import TransactionRequest, TransactionUpdateRequest
from mintapi.trends import ReportView, TrendRequest
from requests import Response

Expand All @@ -36,6 +36,9 @@ def get(self, **kwargs):
def post(self, **kwargs):
return self.request(method="POST", **kwargs)

def put(self, **kwargs):
return self.request(method="PUT", **kwargs)

def _paginate(self, data_key: str, metadata_key: str, response: Response, **kwargs):
"""
Mint API appears to use a limit-offset pagination mechanism with
Expand Down Expand Up @@ -321,6 +324,38 @@ def _get_trend_data(self, **kwargs):
**kwargs,
)

def _get_transaction(self, transaction_id, **kwargs):
api_url = MINT_ROOT_URL
api_section = "/pfm"
uri_path = f"/v1/transactions/{transaction_id}"
metadata_key = None
data_key = None

return self.get(
api_url=api_url,
api_section=api_section,
uri_path=uri_path,
data_key=data_key,
metadata_key=metadata_key,
**kwargs,
)

def _update_transaction(self, transaction_id, **kwargs):
api_url = MINT_ROOT_URL
api_section = "/pfm"
uri_path = f"/v1/transactions/{transaction_id}"
metadata_key = None
data_key = None

return self.put(
api_url=api_url,
api_section=api_section,
uri_path=uri_path,
data_key=data_key,
metadata_key=metadata_key,
**kwargs,
)

"""
User Methods - Add custom postprocessing logic here
"""
Expand Down Expand Up @@ -548,7 +583,7 @@ def get_transaction_data(
remove_pending: bool = True,
limit: int = 1000,
offset: int = 0,
**kwargs
**kwargs,
):
"""
Public accessor for transaction data. Internally constructs a transaction/search api payload
Expand Down Expand Up @@ -639,7 +674,7 @@ def get_trend_data(
match_all_filters: bool = True,
limit: int = 1000,
offset: int = 0,
**kwargs
**kwargs,
):
"""
Public accessor for trend data. Internally constructs a trend api payload
Expand Down Expand Up @@ -695,6 +730,80 @@ def get_trend_data(

return self._get_trend_data(json=payload.to_dict(), **kwargs)

def update_transaction_data(
self,
transaction_id: str,
transaction_type: TransactionUpdateRequest.TransactionType,
description: Optional[str] = None,
notes: Optional[str] = None,
category_id: Optional[str] = None,
tag_ids: List[str] = None,
**kwargs,
) -> Dict:
"""
Updates the data for a given transaction.

Parameters
----------
transaction_id : str
The ID of the transaction to update.
transaction_type : TransactionUpdateRequest.TransactionType
The type of update to perform on the transaction.
description : str, optional
The updated description to assign to the transaction.
notes : str, optional
Optional notes to associate with the transaction.
category_id : str, optional
Optional category ID to assign to the transaction.
tag_ids : List[str], optional
Optional list of tag IDs to assign to the transaction. If provided, the existing tags associated with the transaction
will be replaced with the new tags specified in the payload. Note that if only one tag is included in the list,
the replacement will not be incremental, but instead will completely replace the existing tags with the new tags.
**kwargs
Additional keyword arguments to pass to the update transaction API.

Returns
-------
Dict
The updated transaction data.

Raises
------
RuntimeError
If the update transaction API returns a non-204 status code.
"""

# Construct the payload for the update transaction API
payload = TransactionUpdateRequest(
description=description,
notes=notes,
category_id=category_id,
tag_ids=tag_ids,
type=transaction_type,
)

# Call the update transaction API
update_response = self._update_transaction(
transaction_id=transaction_id,
json=payload.to_dict(),
paginate=False,
**kwargs,
)

# Raise an exception if the update transaction API returned an error
if update_response.status_code != 204:
raise RuntimeError(
f"Update transaction {transaction_id} failed with status code {update_response.status_code}"
)

# Get and return the updated transaction data
transaction_response: Response = self._get_transaction(
transaction_id=transaction_id, paginate=False
)
updated_transaction_data = transaction_response.json()

return updated_transaction_data

"""
Convenience wrappers
"""
Expand Down
64 changes: 64 additions & 0 deletions mintapi/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"""

from dataclasses import dataclass
from enum import Enum
from typing import List

from mintapi.filters import SearchFilter, DateFilter


Expand Down Expand Up @@ -32,3 +35,64 @@ def to_dict(self):
"limit": self.limit,
"offset": self.offset,
}


@dataclass
class TransactionUpdateRequest:
class TransactionType(Enum):
CashAndCreditTransaction = "CashAndCreditTransaction"
InvestmentTransaction = "InvestmentTransaction"
LoanTransaction = "LoanTransaction"

"""
Helper class to construct a transaction update request payload.

Parameters
----------
type : TransactionType
The type of transaction to update.
description : str, optional
Optional description to assign to the transaction.
notes : str, optional
Optional notes to associate with the transaction.
category_id : str, optional
Optional category ID to assign to the transaction.
tag_ids : List[str], optional
Optional list of tag IDs to assign to the transaction.

Raises
------
ValueError
If the transaction type is not one of the allowed values in the `TransactionUpdateRequest.TransactionType` enum.
"""
type: TransactionType
description: str = None
notes: str = None
category_id: str = None
tag_ids: List[str] = None

def __post_init__(self):
if isinstance(self.type, str):
assert hasattr(self.TransactionType, self.type)
else:
raise ValueError(
"Transaction Type must be one of allowed values in enum `TransactionUpdateRequest.TransactionType"
)

def to_dict(self):
payload = {
"type": self.type,
}
if self.description is not None:
payload["description"] = self.description
if self.notes is not None:
payload["notes"] = self.notes
if self.category_id is not None:
payload["category"] = {
"id": self.category_id,
}
if self.tag_ids is not None:
payload["tagData"] = {
"tags": [{"id": tag_id} for tag_id in self.tag_ids],
}
return payload
27 changes: 27 additions & 0 deletions tests/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,33 @@ def test_trend_endpoint(self, mock_request, _):
uri_path="/v1/trends",
)

@patch.object(
mintapi.endpoints.MintEndpoints, "__abstractmethods__", new_callable=set
)
@patch.object(mintapi.endpoints.MintEndpoints, "request")
def test_update_transaction_endpoint(self, mock_request, _):
"""
Tests params are correctly passed to the request method
"""
# Future TODO: mock full api response
mock_request.return_value = None
endpoints = MintEndpoints()
transaction_id = "1"
data = endpoints._update_transaction(
transaction_id="1",
)
self.assertIsNone(data)

# assert pagination call
mock_request.assert_called_once_with(
data_key=None,
metadata_key=None,
api_section="/pfm",
api_url="https://mint.intuit.com",
method="PUT",
uri_path=f"/v1/transactions/{transaction_id}",
)


class UserMethodEndpointTests(unittest.TestCase):
"""
Expand Down