ynab-cli 0.5.2__tar.gz → 0.5.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/CHANGELOG.md +12 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/PKG-INFO +1 -1
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/pyproject.toml +1 -1
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/textual/io.py +12 -14
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/util.py +3 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/models/rules.py +21 -8
- ynab_cli-0.5.4/src/ynab_cli/domain/use_cases/budgets.py +45 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/categories.py +46 -20
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/payees.py +68 -40
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/transactions.py +19 -9
- ynab_cli-0.5.4/src/ynab_cli/host/textual/app.py +120 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/app.tcss +67 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/textual/cli.py +2 -2
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories/list_all.py +38 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories/list_unused.py +38 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories/tabs.py +27 -0
- ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/__init__.py → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/base_command.py +5 -16
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/__init__.py +12 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/dialog.py +63 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/form.py +11 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/screen.py +53 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/runnable_widget.py +11 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/list_all.py +32 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/list_duplicates.py +38 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/list_unused.py +74 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/normalize_names.py +36 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/tabs.py +37 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/transactions/apply_rules.py +118 -0
- ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/transactions/tabs.py +22 -0
- ynab_cli-0.5.4/tests/__init__.py +0 -0
- ynab_cli-0.5.4/tests/factories/__init__.py +0 -0
- ynab_cli-0.5.4/tests/unit/__init__.py +0 -0
- ynab_cli-0.5.4/tests/unit/domain/__init__.py +0 -0
- ynab_cli-0.5.4/tests/unit/domain/use_cases/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/uv.lock +1 -1
- ynab_cli-0.5.2/src/ynab_cli/domain/use_cases/budgets.py +0 -33
- ynab_cli-0.5.2/src/ynab_cli/host/textual/app.py +0 -98
- ynab_cli-0.5.2/src/ynab_cli/host/textual/app.tcss +0 -47
- ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/categories.py +0 -90
- ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/payees.py +0 -154
- ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/transactions.py +0 -89
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.env-sample +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/FUNDING.yml +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/dependabot.yml +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/workflows/pr.yml +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/workflows/publish-to-pypi.yml +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.gitignore +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.python-version +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/LICENSE.md +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/Makefile +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/README.md +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/docs/index.md +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/docs/transaction_rules.md +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/mkdocs.yml +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/rich/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/rich/io.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/textual/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/create_account.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/get_account_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/get_accounts.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/get_budget_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/get_budget_settings_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/get_budgets.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/get_categories.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/get_category_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/get_month_category_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/update_category.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/update_month_category.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/months/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/months/get_budget_month.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/months/get_budget_months.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payee_locations/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_location_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_locations.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_locations_by_payee.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/get_payee_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/get_payees.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/update_payee.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/create_scheduled_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/delete_scheduled_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/get_scheduled_transaction_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/get_scheduled_transactions.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/update_scheduled_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/create_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/delete_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transaction_by_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_account.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_category.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_month.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_payee.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/import_transactions.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/update_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/update_transactions.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/user/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/user/get_user.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/client.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/errors.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/accounts_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/accounts_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_detail.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_detail_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_detail_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_settings.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_settings_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_settings_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_summary.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_summary_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_summary_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_response_data_bulk.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_transactions.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/categories_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/categories_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_2_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_3_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_group.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_group_with_categories.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/currency_format_type_0.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/date_format_type_0.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/error_detail.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/error_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/existing_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_account_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_category_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_month_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_payee_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transaction_type.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transactions_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transactions_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/loan_account_periodic_value_type_0.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_detail.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_detail_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_detail_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_summaries_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_summaries_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_summary.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/new_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_category_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_month_category_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_payee_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_transactions_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_location.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_location_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_location_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_locations_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_locations_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payees_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payees_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/post_account_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/post_scheduled_transaction_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/post_transactions_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/put_scheduled_transaction_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/put_transaction_wrapper.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_account.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_category.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_category_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_category_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_month_category.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_payee.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_payee_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_payee_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_scheduled_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_sub_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transaction_with_id_or_import_id.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transaction_with_optional_fields.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transactions_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transactions_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_sub_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_detail.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_frequency.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_summary.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_summary_frequency.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transactions_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transactions_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/sub_transaction.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_cleared_status.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_detail.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_2_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_3_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_summary.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_summary_debt_transaction_type_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_summary_debt_transaction_type_type_2_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_summary_debt_transaction_type_type_3_type_1.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_import_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_import_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/user.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/user_response.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/user_response_data.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/types.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/constants.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/models/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/ports/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/ports/io.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/settings.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/cli.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/cli.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/budgets.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/categories.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/payees.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/rich/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/rich/progress_table.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/transactions.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/constants.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/textual/__init__.py +0 -0
- {ynab_cli-0.5.2/tests → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets}/__init__.py +0 -0
- {ynab_cli-0.5.2/tests/factories → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories}/__init__.py +0 -0
- {ynab_cli-0.5.2/tests/unit → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common}/__init__.py +0 -0
- {ynab_cli-0.5.2/tests/unit/domain → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees}/__init__.py +0 -0
- {ynab_cli-0.5.2/tests/unit/domain/use_cases → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/transactions}/__init__.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/py.typed +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/factories/ynab.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/conftest.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/domain/use_cases/test_categories.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/domain/use_cases/test_payees.py +0 -0
- {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/domain/use_cases/test_transactions.py +0 -0
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v0.5.4 (2025-06-10)
|
|
9
|
+
|
|
10
|
+
### Fix
|
|
11
|
+
|
|
12
|
+
- **tui**: added some command parameter forms
|
|
13
|
+
|
|
14
|
+
## v0.5.3 (2025-06-09)
|
|
15
|
+
|
|
16
|
+
### Fix
|
|
17
|
+
|
|
18
|
+
- **tui**: moves the app settings into a separate modal dialog
|
|
19
|
+
|
|
8
20
|
## v0.5.2 (2025-06-07)
|
|
9
21
|
|
|
10
22
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ynab-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: YNAB Command Line Interface
|
|
5
5
|
Project-URL: Changelog, https://github.com/jeffsawatzky/ynab-cli/blob/main/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://jeffsawatzky.github.io/ynab-cli/
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from textual import on
|
|
4
3
|
from textual.app import App, ComposeResult
|
|
5
|
-
from textual.containers import Vertical
|
|
6
|
-
from textual.screen import ModalScreen
|
|
7
4
|
from textual.widgets import Input, Label, Log, ProgressBar
|
|
8
5
|
from typing_extensions import override
|
|
9
6
|
|
|
10
7
|
from ynab_cli.domain.ports.io import IO, Progress
|
|
8
|
+
from ynab_cli.host.textual.widgets.common.dialogs import CANCELLED, DialogForm, SaveCancelDialogScreen
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class TextualProgress(Progress):
|
|
@@ -28,9 +26,7 @@ class TextualProgress(Progress):
|
|
|
28
26
|
self._progress_bar.update(**kwargs)
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
class
|
|
32
|
-
"""A simple modal screen which asks for user input."""
|
|
33
|
-
|
|
29
|
+
class IODialogForm(DialogForm[str]):
|
|
34
30
|
def __init__(self, prompt: str, password: bool, *args: Any, **kwargs: Any) -> None:
|
|
35
31
|
super().__init__(*args, **kwargs)
|
|
36
32
|
self._prompt = prompt
|
|
@@ -38,16 +34,15 @@ class IOScreen(ModalScreen[str]):
|
|
|
38
34
|
|
|
39
35
|
@override
|
|
40
36
|
def compose(self) -> ComposeResult:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
yield Input(password=self._password)
|
|
37
|
+
yield Label(self._prompt)
|
|
38
|
+
yield Input(password=self._password)
|
|
44
39
|
|
|
45
|
-
@
|
|
46
|
-
def
|
|
47
|
-
self.
|
|
40
|
+
@override
|
|
41
|
+
async def get_result(self) -> str:
|
|
42
|
+
return self.query_one(Input).value.strip()
|
|
48
43
|
|
|
49
44
|
|
|
50
|
-
class
|
|
45
|
+
class TextualIO(IO):
|
|
51
46
|
def __init__(self, app: App[Any], log: Log, progress_bar: ProgressBar) -> None:
|
|
52
47
|
self._app = app
|
|
53
48
|
self._log = log
|
|
@@ -55,7 +50,10 @@ class TextualWorkerIO(IO):
|
|
|
55
50
|
|
|
56
51
|
@override
|
|
57
52
|
async def prompt(self, prompt: str, password: bool = False) -> str:
|
|
58
|
-
|
|
53
|
+
result = await self._app.push_screen_wait(SaveCancelDialogScreen(IODialogForm(prompt, password), title="Input"))
|
|
54
|
+
if result is CANCELLED:
|
|
55
|
+
return ""
|
|
56
|
+
return result
|
|
59
57
|
|
|
60
58
|
@override
|
|
61
59
|
async def print(self, message: str) -> None:
|
|
@@ -55,6 +55,9 @@ async def run_asyncio_detailed(
|
|
|
55
55
|
new_access_token = await io.prompt(
|
|
56
56
|
prompt="API rate limit exceeded. Enter a new access token", password=True
|
|
57
57
|
)
|
|
58
|
+
if not new_access_token:
|
|
59
|
+
raise e
|
|
60
|
+
|
|
58
61
|
client.token = new_access_token
|
|
59
62
|
response = await asyncio_detailed(*args, **kwargs)
|
|
60
63
|
parsed_data = get_parsed_response_data(response)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any, Self
|
|
2
2
|
|
|
3
3
|
from attrs import define as _attrs_define
|
|
4
4
|
|
|
@@ -18,20 +18,33 @@ class TransactionRule:
|
|
|
18
18
|
# See: https://api.ynab.com/v1#/Transactions/updateTransactions
|
|
19
19
|
patch: models.SaveTransactionWithIdOrImportId | None = None
|
|
20
20
|
|
|
21
|
+
def to_dict(self) -> dict[str, Any]:
|
|
22
|
+
return {
|
|
23
|
+
"rules": self.rules,
|
|
24
|
+
"patch": self.patch.to_dict() if self.patch else None,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
29
|
+
rules = [str(r) for r in data.get("rules", [])]
|
|
30
|
+
patch = None
|
|
31
|
+
if "patch" in data:
|
|
32
|
+
patch = models.SaveTransactionWithIdOrImportId.from_dict(data["patch"])
|
|
33
|
+
|
|
34
|
+
return cls(rules=rules, patch=patch)
|
|
35
|
+
|
|
21
36
|
|
|
22
37
|
@_attrs_define
|
|
23
38
|
class TransactionRules:
|
|
24
39
|
transaction_rules: list[TransactionRule]
|
|
25
40
|
|
|
41
|
+
def to_dict(self) -> dict[str, Any]:
|
|
42
|
+
return {"transaction_rules": [rule.to_dict() for rule in self.transaction_rules]}
|
|
43
|
+
|
|
26
44
|
@classmethod
|
|
27
|
-
def from_dict(cls, data: dict[str, Any]) ->
|
|
45
|
+
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
28
46
|
transaction_rules = []
|
|
29
47
|
for rule in data.get("transaction_rules", []):
|
|
30
|
-
|
|
31
|
-
patch = None
|
|
32
|
-
if "patch" in rule:
|
|
33
|
-
patch = models.SaveTransactionWithIdOrImportId.from_dict(rule["patch"])
|
|
34
|
-
|
|
35
|
-
transaction_rules.append(TransactionRule(rules=rules, patch=patch))
|
|
48
|
+
transaction_rules.append(TransactionRule.from_dict(rule))
|
|
36
49
|
|
|
37
50
|
return cls(transaction_rules=transaction_rules)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator
|
|
2
|
+
from typing import TypedDict
|
|
3
|
+
|
|
4
|
+
from ynab_cli.adapters import ynab
|
|
5
|
+
from ynab_cli.adapters.ynab import models, util
|
|
6
|
+
from ynab_cli.adapters.ynab.api.budgets import get_budgets
|
|
7
|
+
from ynab_cli.domain import ports
|
|
8
|
+
from ynab_cli.domain.constants import YNAB_API_URL
|
|
9
|
+
from ynab_cli.domain.settings import Settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ListAllParams(TypedDict):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def list_all(
|
|
17
|
+
settings: Settings, io: ports.IO, params: ListAllParams, *, client: ynab.AuthenticatedClient | None = None
|
|
18
|
+
) -> AsyncIterator[models.BudgetSummary]:
|
|
19
|
+
try:
|
|
20
|
+
progress_total = 0
|
|
21
|
+
|
|
22
|
+
if client is None:
|
|
23
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
24
|
+
|
|
25
|
+
async with client:
|
|
26
|
+
budgets = (
|
|
27
|
+
await util.get_asyncio_detailed(io, get_budgets.asyncio_detailed, include_accounts=True, client=client)
|
|
28
|
+
).data.budgets
|
|
29
|
+
budgets.sort(key=lambda b: b.name)
|
|
30
|
+
|
|
31
|
+
progress_total = len(budgets)
|
|
32
|
+
await io.progress.update(total=progress_total)
|
|
33
|
+
for budget in budgets:
|
|
34
|
+
await io.progress.update(advance=1)
|
|
35
|
+
yield budget
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
39
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
40
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
41
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
42
|
+
else:
|
|
43
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
44
|
+
finally:
|
|
45
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
@@ -20,9 +20,16 @@ class ListUnusedParams(TypedDict):
|
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
async def list_unused(
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
async def list_unused(
|
|
24
|
+
settings: Settings, io: ports.IO, params: ListUnusedParams, *, client: ynab.AuthenticatedClient | None = None
|
|
25
|
+
) -> AsyncIterator[models.Category]:
|
|
26
|
+
try:
|
|
27
|
+
progress_total = 0
|
|
28
|
+
|
|
29
|
+
if client is None:
|
|
30
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
31
|
+
|
|
32
|
+
async with client:
|
|
26
33
|
category_groups = (
|
|
27
34
|
await util.get_asyncio_detailed(
|
|
28
35
|
io, get_categories.asyncio_detailed, settings.ynab.budget_id, client=client
|
|
@@ -31,16 +38,18 @@ async def list_unused(settings: Settings, io: ports.IO, params: ListUnusedParams
|
|
|
31
38
|
category_groups.sort(key=lambda cg: cg.name)
|
|
32
39
|
|
|
33
40
|
progress_total = len(category_groups)
|
|
41
|
+
await io.progress.update(total=progress_total)
|
|
34
42
|
for category_group in category_groups:
|
|
35
|
-
await io.progress.update(
|
|
43
|
+
await io.progress.update(advance=1)
|
|
36
44
|
|
|
37
45
|
if _should_skip_category_or_group(category_or_group=category_group):
|
|
38
46
|
continue
|
|
39
47
|
|
|
40
48
|
progress_total += len(category_group.categories)
|
|
49
|
+
await io.progress.update(total=progress_total)
|
|
41
50
|
category_group.categories.sort(key=lambda c: c.name)
|
|
42
51
|
for category in category_group.categories:
|
|
43
|
-
await io.progress.update(
|
|
52
|
+
await io.progress.update(advance=1)
|
|
44
53
|
|
|
45
54
|
if _should_skip_category_or_group(category_or_group=category):
|
|
46
55
|
continue
|
|
@@ -60,20 +69,31 @@ async def list_unused(settings: Settings, io: ports.IO, params: ListUnusedParams
|
|
|
60
69
|
if not num_transactions:
|
|
61
70
|
yield category
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
except Exception as e:
|
|
73
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
74
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
75
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
76
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
77
|
+
else:
|
|
78
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
79
|
+
finally:
|
|
80
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
68
81
|
|
|
69
82
|
|
|
70
83
|
class ListAllParams(TypedDict):
|
|
71
84
|
pass
|
|
72
85
|
|
|
73
86
|
|
|
74
|
-
async def list_all(
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
async def list_all(
|
|
88
|
+
settings: Settings, io: ports.IO, params: ListAllParams, *, client: ynab.AuthenticatedClient | None = None
|
|
89
|
+
) -> AsyncIterator[models.Category]:
|
|
90
|
+
try:
|
|
91
|
+
progress_total = 0
|
|
92
|
+
|
|
93
|
+
if client is None:
|
|
94
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
95
|
+
|
|
96
|
+
async with client:
|
|
77
97
|
category_groups = (
|
|
78
98
|
await util.get_asyncio_detailed(
|
|
79
99
|
io, get_categories.asyncio_detailed, settings.ynab.budget_id, client=client
|
|
@@ -82,24 +102,30 @@ async def list_all(settings: Settings, io: ports.IO, params: ListAllParams) -> A
|
|
|
82
102
|
category_groups.sort(key=lambda cg: cg.name)
|
|
83
103
|
|
|
84
104
|
progress_total = len(category_groups)
|
|
105
|
+
await io.progress.update(total=progress_total)
|
|
85
106
|
for category_group in category_groups:
|
|
86
|
-
await io.progress.update(
|
|
107
|
+
await io.progress.update(advance=1)
|
|
87
108
|
|
|
88
109
|
if _should_skip_category_or_group(category_or_group=category_group):
|
|
89
110
|
continue
|
|
90
111
|
|
|
91
112
|
progress_total += len(category_group.categories)
|
|
113
|
+
await io.progress.update(total=progress_total)
|
|
92
114
|
category_group.categories.sort(key=lambda c: c.name)
|
|
93
115
|
for category in category_group.categories:
|
|
94
|
-
await io.progress.update(
|
|
116
|
+
await io.progress.update(advance=1)
|
|
95
117
|
|
|
96
118
|
if _should_skip_category_or_group(category_or_group=category):
|
|
97
119
|
continue
|
|
98
120
|
|
|
99
121
|
yield category
|
|
100
122
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
except Exception as e:
|
|
124
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
125
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
126
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
127
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
128
|
+
else:
|
|
129
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
130
|
+
finally:
|
|
131
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
@@ -47,19 +47,22 @@ class NormalizeNamesParams(TypedDict):
|
|
|
47
47
|
async def normalize_names(
|
|
48
48
|
settings: Settings, io: ports.IO, params: NormalizeNamesParams, *, client: ynab.AuthenticatedClient | None = None
|
|
49
49
|
) -> AsyncIterator[tuple[models.Payee, str]]:
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
try:
|
|
51
|
+
progress_total = 0
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
if client is None:
|
|
54
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
55
|
+
|
|
56
|
+
async with client:
|
|
55
57
|
payees = (
|
|
56
58
|
await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
|
|
57
59
|
).data.payees
|
|
58
60
|
payees.sort(key=lambda p: p.name)
|
|
59
61
|
|
|
60
62
|
progress_total = len(payees)
|
|
63
|
+
await io.progress.update(total=progress_total)
|
|
61
64
|
for payee in payees:
|
|
62
|
-
await io.progress.update(
|
|
65
|
+
await io.progress.update(advance=1)
|
|
63
66
|
|
|
64
67
|
if _should_skip_payee(payee=payee):
|
|
65
68
|
continue
|
|
@@ -78,11 +81,15 @@ async def normalize_names(
|
|
|
78
81
|
body=models.PatchPayeeWrapper(payee=models.SavePayee(name=normalized_name)),
|
|
79
82
|
)
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
except Exception as e:
|
|
85
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
86
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
87
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
88
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
89
|
+
else:
|
|
90
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
91
|
+
finally:
|
|
92
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
86
93
|
|
|
87
94
|
|
|
88
95
|
class ListDuplicatesParams(TypedDict):
|
|
@@ -92,11 +99,13 @@ class ListDuplicatesParams(TypedDict):
|
|
|
92
99
|
async def list_duplicates(
|
|
93
100
|
settings: Settings, io: ports.IO, params: ListDuplicatesParams, *, client: ynab.AuthenticatedClient | None = None
|
|
94
101
|
) -> AsyncIterator[tuple[models.Payee, models.Payee]]:
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
try:
|
|
103
|
+
progress_total = 0
|
|
104
|
+
|
|
105
|
+
if client is None:
|
|
106
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
try:
|
|
108
|
+
async with client:
|
|
100
109
|
possible_duplicates: dict[tuple[UUID, str], list[tuple[UUID, str]]] = {}
|
|
101
110
|
|
|
102
111
|
payees = (
|
|
@@ -105,8 +114,9 @@ async def list_duplicates(
|
|
|
105
114
|
payees.sort(key=lambda p: p.name)
|
|
106
115
|
|
|
107
116
|
progress_total = len(payees)
|
|
117
|
+
await io.progress.update(total=progress_total)
|
|
108
118
|
for idx, payee in enumerate(payees):
|
|
109
|
-
await io.progress.update(
|
|
119
|
+
await io.progress.update(advance=1)
|
|
110
120
|
|
|
111
121
|
if _should_skip_payee(payee=payee):
|
|
112
122
|
continue
|
|
@@ -134,11 +144,15 @@ async def list_duplicates(
|
|
|
134
144
|
|
|
135
145
|
yield (payee, filtered_payee)
|
|
136
146
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
except Exception as e:
|
|
148
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
149
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
150
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
151
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
152
|
+
else:
|
|
153
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
154
|
+
finally:
|
|
155
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
142
156
|
|
|
143
157
|
|
|
144
158
|
class ListUnusedParams(TypedDict):
|
|
@@ -148,19 +162,22 @@ class ListUnusedParams(TypedDict):
|
|
|
148
162
|
async def list_unused(
|
|
149
163
|
settings: Settings, io: ports.IO, params: ListUnusedParams, *, client: ynab.AuthenticatedClient | None = None
|
|
150
164
|
) -> AsyncIterator[models.Payee]:
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
try:
|
|
166
|
+
progress_total = 0
|
|
153
167
|
|
|
154
|
-
|
|
155
|
-
|
|
168
|
+
if client is None:
|
|
169
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
170
|
+
|
|
171
|
+
async with client:
|
|
156
172
|
payees = (
|
|
157
173
|
await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
|
|
158
174
|
).data.payees
|
|
159
175
|
payees.sort(key=lambda p: p.name)
|
|
160
176
|
|
|
161
177
|
progress_total = len(payees)
|
|
178
|
+
await io.progress.update(total=progress_total)
|
|
162
179
|
for payee in payees:
|
|
163
|
-
await io.progress.update(
|
|
180
|
+
await io.progress.update(advance=1)
|
|
164
181
|
|
|
165
182
|
if _should_skip_payee(payee=payee):
|
|
166
183
|
continue
|
|
@@ -192,11 +209,15 @@ async def list_unused(
|
|
|
192
209
|
body=models.PatchPayeeWrapper(payee=models.SavePayee(name=new_name)),
|
|
193
210
|
)
|
|
194
211
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
212
|
+
except Exception as e:
|
|
213
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
214
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
215
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
216
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
217
|
+
else:
|
|
218
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
219
|
+
finally:
|
|
220
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
200
221
|
|
|
201
222
|
|
|
202
223
|
class ListAllParams(TypedDict):
|
|
@@ -206,27 +227,34 @@ class ListAllParams(TypedDict):
|
|
|
206
227
|
async def list_all(
|
|
207
228
|
settings: Settings, io: ports.IO, params: ListAllParams, *, client: ynab.AuthenticatedClient | None = None
|
|
208
229
|
) -> AsyncIterator[models.Payee]:
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
try:
|
|
231
|
+
progress_total = 0
|
|
232
|
+
|
|
233
|
+
if client is None:
|
|
234
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
211
235
|
|
|
212
|
-
|
|
213
|
-
try:
|
|
236
|
+
async with client:
|
|
214
237
|
payees = (
|
|
215
238
|
await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
|
|
216
239
|
).data.payees
|
|
217
240
|
payees.sort(key=lambda p: p.name)
|
|
218
241
|
|
|
219
242
|
progress_total = len(payees)
|
|
243
|
+
await io.progress.update(total=progress_total)
|
|
220
244
|
for payee in payees:
|
|
221
|
-
await io.progress.update(
|
|
245
|
+
await io.progress.update(advance=1)
|
|
222
246
|
|
|
223
247
|
if _should_skip_payee(payee=payee):
|
|
224
248
|
continue
|
|
225
249
|
|
|
226
250
|
yield payee
|
|
227
251
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
except Exception as e:
|
|
253
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
254
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
255
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
256
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
257
|
+
else:
|
|
258
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
259
|
+
finally:
|
|
260
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|
|
@@ -44,10 +44,15 @@ class ApplyRulesParams(TypedDict):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
async def apply_rules(
|
|
47
|
-
settings: Settings, io: ports.IO, params: ApplyRulesParams
|
|
47
|
+
settings: Settings, io: ports.IO, params: ApplyRulesParams, *, client: ynab.AuthenticatedClient | None = None
|
|
48
48
|
) -> AsyncIterator[tuple[models.TransactionDetail, models.SaveTransactionWithIdOrImportId]]:
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
try:
|
|
50
|
+
progress_total = 0
|
|
51
|
+
|
|
52
|
+
if client is None:
|
|
53
|
+
client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
|
|
54
|
+
|
|
55
|
+
async with client:
|
|
51
56
|
transactions = (
|
|
52
57
|
await util.get_asyncio_detailed(
|
|
53
58
|
io,
|
|
@@ -62,8 +67,9 @@ async def apply_rules(
|
|
|
62
67
|
save_transactions = []
|
|
63
68
|
|
|
64
69
|
progress_total = len(transactions)
|
|
70
|
+
await io.progress.update(total=progress_total)
|
|
65
71
|
for transaction in transactions:
|
|
66
|
-
await io.progress.update(
|
|
72
|
+
await io.progress.update(advance=1)
|
|
67
73
|
|
|
68
74
|
if _should_skip_transaction(transaction=transaction):
|
|
69
75
|
continue
|
|
@@ -86,8 +92,12 @@ async def apply_rules(
|
|
|
86
92
|
body=models.PatchTransactionsWrapper(transactions=save_transactions),
|
|
87
93
|
)
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
except Exception as e:
|
|
96
|
+
if isinstance(e, util.ApiError) and e.status_code == 401:
|
|
97
|
+
await io.print("Invalid or expired access token. Please update your settings.")
|
|
98
|
+
elif isinstance(e, util.ApiError) and e.status_code == 429:
|
|
99
|
+
await io.print("API rate limit exceeded. Try again later, or get a new access token.")
|
|
100
|
+
else:
|
|
101
|
+
await io.print(f"Exception when calling YNAB: {e}\n")
|
|
102
|
+
finally:
|
|
103
|
+
await io.progress.update(total=progress_total, completed=progress_total)
|