-
-
Notifications
You must be signed in to change notification settings - Fork 94
Improve upload account balance history #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Improve upload account balance history #86
Conversation
hammem
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @CalvinChanCan ! What's the difference between using this and uploading transactions to an account? I don't understand how this will appear different to a person in the UX once it's done.
I have a bunch of inline comments about the naming of methods, ensuring it's consistent with patterns already set in the API, and ensuring you don't accidentally include your changes from #85
| - `set_budget_amount` - sets a budget's value to the given amount (date allowed, will only apply to month specified by default). A zero amount value will "unset" or "clear" the budget for the given category. | ||
| - `create_manual_account` - creates a new manual account | ||
| - `upload_account_balance_history` - uploads account history csv file for a given account | ||
| - `delete_account` - deletes an account by the provided account id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please pull this branch off of your other commits in the other PR, so it doesn't accidentally introduce those changes simultaneously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
| variables=variables, | ||
| ) | ||
|
|
||
| async def delete_account( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above, please stack these commits atop origin/main, instead of the other branch you've created for #85
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
monarchmoney/monarchmoney.py
Outdated
|
|
||
| :param account_id: The account ID to apply the history to. | ||
| :param csv_content: CSV representation of the balance history. | ||
| :param csv_content: CSV representation of the balance history. Headers: Date, Amount, and Account Name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move this description into the body of the docblock, instead of here, where it's challenging to fit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
monarchmoney/monarchmoney.py
Outdated
| response = await resp.json() | ||
| session_key = response["session_key"] | ||
| return session_key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return the entire JSON block, for consistency with the rest of the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactor this quite a bit since the initial review so I've added _upload_form_data as a way to generalize the uploading form data (I'm hoping to use this later to bulk add transactions from a csv). Anyway, this should now return the whole response.
monarchmoney/monarchmoney.py
Outdated
| Uploads and parses the balance history, updating the account balance on monarch money | ||
|
|
||
| :param account_id: The account ID to apply the history to. | ||
| :param csv_content: CSV representation of the balance history. Headers are Date, Amount, and Account Name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be something more structured than a str? If there are specific columns and column types/formats needed, perhaps using a List[Dict[str, Any]]? Alternatively, use either Python's built-in CSV parsing library or, maybe, Pandas.
nit: move the description of the CSV format required to the body of the docblock
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're going this far to normalize the input, I'd vote for making the list value a class (or DataClass/NamedTuple). This way the caller doesn't need to worry about what the magic key names are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that a dataclass would help the caller with the proper input. I've refactor it to use a dataclass. A second review over this would be appreciated!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'm fine with providing the CSV as a string. This gives the user the most flexibility to use the tools of their choice to build the CSV. If we require a normalized input, then they'll have to use a CSV parser to parse the input, only for it to generate a CSV once again.
Optionally, we could provide users with a CSV builder kind of class if we want to help them along. IMO this could be an improvement made outside of this PR. That's @hammem 's call :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the good discussion on this one! Yeah, it's not ideal no matter which route you take. If only we knew why @monarchmoney decided to not use a GraphQL endpoint for uploading this data...
The option to go with a dataclass is great and probably something to do elsewhere.
My main concern with a raw CSV string is it makes debugging a huge pain for end users, as we can't guide or hint where things might be off.
But, I've been holding this up for a while, no need to keep holding it up for that.
Appreciate everyone's patience!
monarchmoney/monarchmoney.py
Outdated
| async def upload_and_parse_balance_history( | ||
| self, | ||
| account_id: str, | ||
| csv_content, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs a typehint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep upload_account_balance_history, I've removed upload_and_parse_balance_history so this isn't necessary anymore.
monarchmoney/monarchmoney.py
Outdated
| session_key = response["session_key"] | ||
| return session_key | ||
|
|
||
| async def parse_upload_balance_history_session(self, session_key: str) -> dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| async def parse_upload_balance_history_session(self, session_key: str) -> dict: | |
| async def initiate_upload_balance_history_session(self, session_key: str) -> dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
monarchmoney/monarchmoney.py
Outdated
| timeout: int = 300, | ||
| delay: int = 10, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
define these as constants, so it can be consistent across the API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
monarchmoney/monarchmoney.py
Outdated
| "Web_ParseUploadBalanceHistorySession", query, variables | ||
| ) | ||
|
|
||
| async def get_upload_balance_history_session(self, session_key: str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency with similar methods in this API
| async def get_upload_balance_history_session(self, session_key: str): | |
| async def is_upload_balance_history_complete(self, session_key: str) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
monarchmoney/monarchmoney.py
Outdated
|
|
||
| is_completed = (await self.get_upload_balance_history_session(session_key))[ | ||
| "uploadBalanceHistorySession" | ||
| ]["status"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is missing a == "completed" check.
Though, I believe a better approach would be to check whether the processing is still "pending" so that this code short circuits if the upload fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is missing the check! Thanks for catching that!
The possible statuses that I've found so far are: "created", "started", and "completed". After calling _initiate_upload_balance_history_session, it usually returns the "created" status. Then _is_upload_balance_history_complete can either return the "started" or "completed" statuses.
If _is_upload_balance_history_complete can return "created", it may lead to a premature completed check. Though I don't think that's likely.
@hammem , thoughts on whether we should check for "started" vs "completed"? I could see the better case being checking for "started" but worried about possible side effects that may cause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I was expecting a failed state if an invalid csv is provided. In that case, looks like the following error is thrown. Which I think is sufficient. Please disregard the suggestion!
gql.transport.exceptions.TransportQueryError: {'message': "Something went wrong while processing: ['parseBalanceHistory']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CalvinChanCan , after looking through the flow, I think you've picked the better option. the other can introduce more complicated handling for end-users.
This API allows you to set the balance history on an account irrespective of the transactions. For my use case, I use this API to sync nightly the value of my brokerage accounts with Monarch. There's no transactions to report, the value just fluctuates every day. @CalvinChanCan - Thanks for putting this PR together! I've been meaning to contribute back some local hacks I had to account for the changes made to the API... but this PR is much more complete! |
README.md
Outdated
| - `create_manual_account` - creates a new manual account | ||
| - `upload_account_balance_history` - uploads account history csv file for a given account | ||
| - `delete_account` - deletes an account by the provided account id | ||
| - `upload_and_parse_balance_history` - uploads and parses account history csv file for a given account |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd vote for keeping the existing method rather than make a breaking change. The fact that the API now requires us to poll for the result is an implementation detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @andrecloutier. That's a fair point. I did a bunch of refactoring to keep the existing method upload_account_balance_history.
…o feat-upload-balance-history
…o feat-upload-balance-history # Conflicts: # monarchmoney/monarchmoney.py
…ad_balance_history_session
…e_history_complete
andrecloutier
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! I took these changes for a test drive locally and was able to use it to update some balances in my account.
monarchmoney/monarchmoney.py
Outdated
| csv_content: str, | ||
| timeout: int = TIMEOUT, | ||
| delay: int = DELAY, | ||
| ) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Response is a bool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. thanks for catching this!
|
Hi @hammem, just wondering if I could get another review. Happy to make any further changes/feedback. |
|
I pulled down this code to try and use it. can you please give example input? How do I use BalanceHistoryRow? |
|
Hi @CFarzaneh , You can import the class Example: |
|
@CalvinChanCan , apologies for the delay on this. Just a couple of minor things and this is ready to go. Once you are ready, I'll merge and do a release with this as well. |
| """Performs multi-factor authentication to access a Monarch Money account.""" | ||
| await self._multi_factor_authenticate(email, password, code) | ||
|
|
||
| async def _upload_form_data(self, url: str, data: FormData) -> dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please move methods like this down to the bottom for folks that happen to read the code as an end-user, as _gql_call() and others are.
monarchmoney/monarchmoney.py
Outdated
| Uploads and parses the balance history, updating the account balance on monarch money | ||
|
|
||
| :param account_id: The account ID to apply the history to. | ||
| :param csv_content: CSV representation of the balance history. Headers are Date, Amount, and Account Name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the good discussion on this one! Yeah, it's not ideal no matter which route you take. If only we knew why @monarchmoney decided to not use a GraphQL endpoint for uploading this data...
The option to go with a dataclass is great and probably something to do elsewhere.
My main concern with a raw CSV string is it makes debugging a huge pain for end users, as we can't guide or hint where things might be off.
But, I've been holding this up for a while, no need to keep holding it up for that.
Appreciate everyone's patience!
monarchmoney/monarchmoney.py
Outdated
|
|
||
| is_completed = (await self.get_upload_balance_history_session(session_key))[ | ||
| "uploadBalanceHistorySession" | ||
| ]["status"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CalvinChanCan , after looking through the flow, I think you've picked the better option. the other can introduce more complicated handling for end-users.
|
@CalvinChanCan , lmk if you'd like this included in the next release to PyPI! |
This PR improves on the upload account balance history.
The method
upload_account_balance_historyonly uploads the CSV file but does not update the balance in Monarch Money.A new method
parse_upload_balance_history_sessionis added to parse the csv file which would cause an update to the account balance history.Another new method
get_upload_balance_history_sessionis added to check the status of whether the parsing is still processing or completed.Finally
upload_and_parse_balance_historyuses these 3 methods together to upload and parse the account balance history so that it shows up in Monarch Money.