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.
Files changed (253) hide show
  1. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/CHANGELOG.md +12 -0
  2. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/PKG-INFO +1 -1
  3. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/pyproject.toml +1 -1
  4. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/textual/io.py +12 -14
  5. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/util.py +3 -0
  6. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/models/rules.py +21 -8
  7. ynab_cli-0.5.4/src/ynab_cli/domain/use_cases/budgets.py +45 -0
  8. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/categories.py +46 -20
  9. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/payees.py +68 -40
  10. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/transactions.py +19 -9
  11. ynab_cli-0.5.4/src/ynab_cli/host/textual/app.py +120 -0
  12. ynab_cli-0.5.4/src/ynab_cli/host/textual/app.tcss +67 -0
  13. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/textual/cli.py +2 -2
  14. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories/list_all.py +38 -0
  15. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories/list_unused.py +38 -0
  16. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories/tabs.py +27 -0
  17. 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
  18. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/__init__.py +12 -0
  19. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/dialog.py +63 -0
  20. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/form.py +11 -0
  21. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/dialogs/screen.py +53 -0
  22. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common/runnable_widget.py +11 -0
  23. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/list_all.py +32 -0
  24. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/list_duplicates.py +38 -0
  25. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/list_unused.py +74 -0
  26. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/normalize_names.py +36 -0
  27. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees/tabs.py +37 -0
  28. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/transactions/apply_rules.py +118 -0
  29. ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/transactions/tabs.py +22 -0
  30. ynab_cli-0.5.4/tests/__init__.py +0 -0
  31. ynab_cli-0.5.4/tests/factories/__init__.py +0 -0
  32. ynab_cli-0.5.4/tests/unit/__init__.py +0 -0
  33. ynab_cli-0.5.4/tests/unit/domain/__init__.py +0 -0
  34. ynab_cli-0.5.4/tests/unit/domain/use_cases/__init__.py +0 -0
  35. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/uv.lock +1 -1
  36. ynab_cli-0.5.2/src/ynab_cli/domain/use_cases/budgets.py +0 -33
  37. ynab_cli-0.5.2/src/ynab_cli/host/textual/app.py +0 -98
  38. ynab_cli-0.5.2/src/ynab_cli/host/textual/app.tcss +0 -47
  39. ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/categories.py +0 -90
  40. ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/payees.py +0 -154
  41. ynab_cli-0.5.2/src/ynab_cli/host/textual/widgets/transactions.py +0 -89
  42. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.env-sample +0 -0
  43. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/FUNDING.yml +0 -0
  44. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/dependabot.yml +0 -0
  45. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/workflows/pr.yml +0 -0
  46. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.github/workflows/publish-to-pypi.yml +0 -0
  47. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.gitignore +0 -0
  48. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/.python-version +0 -0
  49. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/LICENSE.md +0 -0
  50. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/Makefile +0 -0
  51. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/README.md +0 -0
  52. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/docs/index.md +0 -0
  53. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/docs/transaction_rules.md +0 -0
  54. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/mkdocs.yml +0 -0
  55. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/__init__.py +0 -0
  56. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/__init__.py +0 -0
  57. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/rich/__init__.py +0 -0
  58. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/rich/io.py +0 -0
  59. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/textual/__init__.py +0 -0
  60. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/__init__.py +0 -0
  61. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/__init__.py +0 -0
  62. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/__init__.py +0 -0
  63. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/create_account.py +0 -0
  64. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/get_account_by_id.py +0 -0
  65. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/accounts/get_accounts.py +0 -0
  66. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/__init__.py +0 -0
  67. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/get_budget_by_id.py +0 -0
  68. {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
  69. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/budgets/get_budgets.py +0 -0
  70. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/__init__.py +0 -0
  71. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/get_categories.py +0 -0
  72. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/get_category_by_id.py +0 -0
  73. {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
  74. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/update_category.py +0 -0
  75. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/categories/update_month_category.py +0 -0
  76. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/months/__init__.py +0 -0
  77. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/months/get_budget_month.py +0 -0
  78. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/months/get_budget_months.py +0 -0
  79. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payee_locations/__init__.py +0 -0
  80. {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
  81. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_locations.py +0 -0
  82. {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
  83. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/__init__.py +0 -0
  84. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/get_payee_by_id.py +0 -0
  85. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/get_payees.py +0 -0
  86. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/payees/update_payee.py +0 -0
  87. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/__init__.py +0 -0
  88. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/create_scheduled_transaction.py +0 -0
  89. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/delete_scheduled_transaction.py +0 -0
  90. {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
  91. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/get_scheduled_transactions.py +0 -0
  92. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/update_scheduled_transaction.py +0 -0
  93. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/__init__.py +0 -0
  94. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/create_transaction.py +0 -0
  95. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/delete_transaction.py +0 -0
  96. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transaction_by_id.py +0 -0
  97. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions.py +0 -0
  98. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_account.py +0 -0
  99. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_category.py +0 -0
  100. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_month.py +0 -0
  101. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_payee.py +0 -0
  102. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/import_transactions.py +0 -0
  103. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/update_transaction.py +0 -0
  104. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/transactions/update_transactions.py +0 -0
  105. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/user/__init__.py +0 -0
  106. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/api/user/get_user.py +0 -0
  107. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/client.py +0 -0
  108. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/errors.py +0 -0
  109. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/__init__.py +0 -0
  110. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account.py +0 -0
  111. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account_response.py +0 -0
  112. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account_response_data.py +0 -0
  113. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/account_type.py +0 -0
  114. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/accounts_response.py +0 -0
  115. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/accounts_response_data.py +0 -0
  116. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_detail.py +0 -0
  117. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_detail_response.py +0 -0
  118. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_detail_response_data.py +0 -0
  119. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_settings.py +0 -0
  120. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_settings_response.py +0 -0
  121. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_settings_response_data.py +0 -0
  122. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_summary.py +0 -0
  123. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_summary_response.py +0 -0
  124. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/budget_summary_response_data.py +0 -0
  125. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_response.py +0 -0
  126. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_response_data.py +0 -0
  127. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_response_data_bulk.py +0 -0
  128. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/bulk_transactions.py +0 -0
  129. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/categories_response.py +0 -0
  130. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/categories_response_data.py +0 -0
  131. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category.py +0 -0
  132. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_1.py +0 -0
  133. {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
  134. {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
  135. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_group.py +0 -0
  136. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_group_with_categories.py +0 -0
  137. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_response.py +0 -0
  138. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/category_response_data.py +0 -0
  139. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/currency_format_type_0.py +0 -0
  140. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/date_format_type_0.py +0 -0
  141. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/error_detail.py +0 -0
  142. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/error_response.py +0 -0
  143. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/existing_transaction.py +0 -0
  144. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_account_type.py +0 -0
  145. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_category_type.py +0 -0
  146. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_month_type.py +0 -0
  147. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_by_payee_type.py +0 -0
  148. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/get_transactions_type.py +0 -0
  149. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transaction.py +0 -0
  150. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transaction_type.py +0 -0
  151. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transactions_response.py +0 -0
  152. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/hybrid_transactions_response_data.py +0 -0
  153. {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
  154. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_detail.py +0 -0
  155. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_detail_response.py +0 -0
  156. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_detail_response_data.py +0 -0
  157. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_summaries_response.py +0 -0
  158. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_summaries_response_data.py +0 -0
  159. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/month_summary.py +0 -0
  160. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/new_transaction.py +0 -0
  161. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_category_wrapper.py +0 -0
  162. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_month_category_wrapper.py +0 -0
  163. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_payee_wrapper.py +0 -0
  164. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/patch_transactions_wrapper.py +0 -0
  165. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee.py +0 -0
  166. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_location.py +0 -0
  167. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_location_response.py +0 -0
  168. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_location_response_data.py +0 -0
  169. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_locations_response.py +0 -0
  170. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_locations_response_data.py +0 -0
  171. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_response.py +0 -0
  172. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payee_response_data.py +0 -0
  173. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payees_response.py +0 -0
  174. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/payees_response_data.py +0 -0
  175. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/post_account_wrapper.py +0 -0
  176. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/post_scheduled_transaction_wrapper.py +0 -0
  177. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/post_transactions_wrapper.py +0 -0
  178. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/put_scheduled_transaction_wrapper.py +0 -0
  179. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/put_transaction_wrapper.py +0 -0
  180. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_account.py +0 -0
  181. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_category.py +0 -0
  182. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_category_response.py +0 -0
  183. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_category_response_data.py +0 -0
  184. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_month_category.py +0 -0
  185. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_payee.py +0 -0
  186. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_payee_response.py +0 -0
  187. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_payee_response_data.py +0 -0
  188. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_scheduled_transaction.py +0 -0
  189. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_sub_transaction.py +0 -0
  190. {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
  191. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transaction_with_optional_fields.py +0 -0
  192. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transactions_response.py +0 -0
  193. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/save_transactions_response_data.py +0 -0
  194. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_sub_transaction.py +0 -0
  195. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_detail.py +0 -0
  196. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_frequency.py +0 -0
  197. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_response.py +0 -0
  198. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_response_data.py +0 -0
  199. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_summary.py +0 -0
  200. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_summary_frequency.py +0 -0
  201. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transactions_response.py +0 -0
  202. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/scheduled_transactions_response_data.py +0 -0
  203. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/sub_transaction.py +0 -0
  204. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_cleared_status.py +0 -0
  205. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_detail.py +0 -0
  206. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_1.py +0 -0
  207. {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
  208. {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
  209. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_response.py +0 -0
  210. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_response_data.py +0 -0
  211. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transaction_summary.py +0 -0
  212. {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
  213. {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
  214. {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
  215. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_import_response.py +0 -0
  216. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_import_response_data.py +0 -0
  217. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_response.py +0 -0
  218. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/transactions_response_data.py +0 -0
  219. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/user.py +0 -0
  220. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/user_response.py +0 -0
  221. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/models/user_response_data.py +0 -0
  222. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/adapters/ynab/types.py +0 -0
  223. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/__init__.py +0 -0
  224. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/constants.py +0 -0
  225. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/models/__init__.py +0 -0
  226. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/ports/__init__.py +0 -0
  227. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/ports/io.py +0 -0
  228. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/settings.py +0 -0
  229. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/domain/use_cases/__init__.py +0 -0
  230. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/__init__.py +0 -0
  231. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/cli.py +0 -0
  232. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/__init__.py +0 -0
  233. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/cli.py +0 -0
  234. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/__init__.py +0 -0
  235. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/budgets.py +0 -0
  236. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/categories.py +0 -0
  237. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/payees.py +0 -0
  238. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/rich/__init__.py +0 -0
  239. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/rich/progress_table.py +0 -0
  240. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/click/commands/transactions.py +0 -0
  241. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/constants.py +0 -0
  242. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/host/textual/__init__.py +0 -0
  243. {ynab_cli-0.5.2/tests → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets}/__init__.py +0 -0
  244. {ynab_cli-0.5.2/tests/factories → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/categories}/__init__.py +0 -0
  245. {ynab_cli-0.5.2/tests/unit → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/common}/__init__.py +0 -0
  246. {ynab_cli-0.5.2/tests/unit/domain → ynab_cli-0.5.4/src/ynab_cli/host/textual/widgets/payees}/__init__.py +0 -0
  247. {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
  248. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/src/ynab_cli/py.typed +0 -0
  249. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/factories/ynab.py +0 -0
  250. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/conftest.py +0 -0
  251. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/domain/use_cases/test_categories.py +0 -0
  252. {ynab_cli-0.5.2 → ynab_cli-0.5.4}/tests/unit/domain/use_cases/test_payees.py +0 -0
  253. {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.2
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "ynab-cli"
3
- version = "0.5.2"
3
+ version = "0.5.4"
4
4
  description = "YNAB Command Line Interface"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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 IOScreen(ModalScreen[str]):
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
- with Vertical():
42
- yield Label(self._prompt)
43
- yield Input(password=self._password)
37
+ yield Label(self._prompt)
38
+ yield Input(password=self._password)
44
39
 
45
- @on(Input.Submitted)
46
- def return_response(self, event: Input.Submitted) -> None:
47
- self.dismiss(event.value)
40
+ @override
41
+ async def get_result(self) -> str:
42
+ return self.query_one(Input).value.strip()
48
43
 
49
44
 
50
- class TextualWorkerIO(IO):
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
- return await self._app.push_screen_wait(IOScreen(prompt, password))
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]) -> "TransactionRules":
45
+ def from_dict(cls, data: dict[str, Any]) -> Self:
28
46
  transaction_rules = []
29
47
  for rule in data.get("transaction_rules", []):
30
- rules = [str(r) for r in rule.get("rules", [])]
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(settings: Settings, io: ports.IO, params: ListUnusedParams) -> AsyncIterator[models.Category]:
24
- async with ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token) as client:
25
- try:
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(total=progress_total, advance=1)
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
64
- if e.status_code == 429:
65
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
66
- else:
67
- await io.print(f"Exception when calling YNAB: {e}\n")
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(settings: Settings, io: ports.IO, params: ListAllParams) -> AsyncIterator[models.Category]:
75
- async with ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token) as client:
76
- try:
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(total=progress_total, advance=1)
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
102
- if e.status_code == 429:
103
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
104
- else:
105
- await io.print(f"Exception when calling YNAB: {e}\n")
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
- if client is None:
51
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
50
+ try:
51
+ progress_total = 0
52
52
 
53
- async with client:
54
- try:
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
82
- if e.status_code == 429:
83
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
84
- else:
85
- await io.print(f"Exception when calling YNAB: {e}\n")
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
- if client is None:
96
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
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
- async with client:
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
138
- if e.status_code == 429:
139
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
140
- else:
141
- await io.print(f"Exception when calling YNAB: {e}\n")
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
- if client is None:
152
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
165
+ try:
166
+ progress_total = 0
153
167
 
154
- async with client:
155
- try:
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
196
- if e.status_code == 429:
197
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
198
- else:
199
- await io.print(f"Exception when calling YNAB: {e}\n")
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
- if client is None:
210
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
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
- async with client:
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
229
- if e.status_code == 429:
230
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
231
- else:
232
- await io.print(f"Exception when calling YNAB: {e}\n")
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
- async with ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token) as client:
50
- try:
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(total=progress_total, advance=1)
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
- except util.ApiError as e:
90
- if e.status_code == 429:
91
- await io.print("API rate limit exceeded. Try again later, or get a new access token.")
92
- else:
93
- await io.print(f"Exception when calling YNAB: {e}\n")
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)