ynab-cli 0.6.0__tar.gz → 0.6.2__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 (287) hide show
  1. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/CHANGELOG.md +16 -0
  2. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/Makefile +1 -1
  3. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/PKG-INFO +3 -2
  4. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/pyproject.toml +3 -2
  5. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/rich/io.py +5 -5
  6. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/client.py +10 -0
  7. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/util.py +2 -1
  8. ynab_cli-0.6.2/src/ynab_cli/domain/use_cases/budgets.py +47 -0
  9. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/use_cases/categories.py +48 -47
  10. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/use_cases/payees.py +103 -87
  11. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/use_cases/transactions.py +26 -24
  12. ynab_cli-0.6.2/src/ynab_cli/host/click/commands/budgets.py +69 -0
  13. ynab_cli-0.6.2/src/ynab_cli/host/click/commands/categories.py +129 -0
  14. ynab_cli-0.6.2/src/ynab_cli/host/click/commands/payees.py +225 -0
  15. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/click/commands/rich/progress_table.py +2 -2
  16. ynab_cli-0.6.2/src/ynab_cli/host/click/commands/transactions.py +94 -0
  17. ynab_cli-0.6.2/src/ynab_cli/host/click/container.py +88 -0
  18. ynab_cli-0.6.2/src/ynab_cli/host/container.py +68 -0
  19. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/app.py +13 -1
  20. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/cli.py +8 -5
  21. ynab_cli-0.6.2/src/ynab_cli/host/textual/container.py +40 -0
  22. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/categories/list_all.py +8 -1
  23. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/categories/list_unused.py +8 -1
  24. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/payees/list_all.py +8 -1
  25. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/payees/list_duplicates.py +8 -3
  26. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/payees/list_unused.py +7 -2
  27. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/payees/normalize_names.py +8 -3
  28. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/transactions/apply_rules.py +7 -4
  29. ynab_cli-0.6.2/tests/factories/ynab.py +18 -0
  30. ynab_cli-0.6.2/tests/unit/adapters/rich/test_io.py +177 -0
  31. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/use_cases/test_budgets.py +4 -2
  32. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/use_cases/test_categories.py +4 -4
  33. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/use_cases/test_payees.py +8 -8
  34. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/use_cases/test_transactions.py +2 -2
  35. ynab_cli-0.6.2/tests/unit/host/click/commands/test_budgets.py +25 -0
  36. ynab_cli-0.6.2/tests/unit/host/click/commands/test_categories.py +64 -0
  37. ynab_cli-0.6.2/tests/unit/host/click/commands/test_payees.py +107 -0
  38. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/click/commands/test_transactions.py +26 -28
  39. ynab_cli-0.6.2/tests/unit/host/click/conftest.py +21 -0
  40. ynab_cli-0.6.2/tests/unit/host/conftest.py +13 -0
  41. ynab_cli-0.6.2/tests/unit/host/textual/conftest.py +21 -0
  42. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/textual/test_app.py +6 -1
  43. ynab_cli-0.6.2/tests/unit/host/textual/test_cli.py +19 -0
  44. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/uv.lock +191 -165
  45. ynab_cli-0.6.0/src/ynab_cli/domain/use_cases/budgets.py +0 -45
  46. ynab_cli-0.6.0/src/ynab_cli/host/click/commands/budgets.py +0 -57
  47. ynab_cli-0.6.0/src/ynab_cli/host/click/commands/categories.py +0 -103
  48. ynab_cli-0.6.0/src/ynab_cli/host/click/commands/payees.py +0 -177
  49. ynab_cli-0.6.0/src/ynab_cli/host/click/commands/transactions.py +0 -85
  50. ynab_cli-0.6.0/tests/factories/ynab.py +0 -18
  51. ynab_cli-0.6.0/tests/unit/adapters/rich/test_io.py +0 -128
  52. ynab_cli-0.6.0/tests/unit/host/click/commands/test_budgets.py +0 -28
  53. ynab_cli-0.6.0/tests/unit/host/click/commands/test_categories.py +0 -66
  54. ynab_cli-0.6.0/tests/unit/host/click/commands/test_payees.py +0 -108
  55. ynab_cli-0.6.0/tests/unit/host/textual/test_cli.py +0 -21
  56. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.env-sample +0 -0
  57. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.github/.copilot-instructions.md +0 -0
  58. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.github/FUNDING.yml +0 -0
  59. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.github/dependabot.yml +0 -0
  60. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.github/workflows/pr.yml +0 -0
  61. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.github/workflows/publish-to-pypi.yml +0 -0
  62. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.gitignore +0 -0
  63. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/.python-version +0 -0
  64. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/LICENSE.md +0 -0
  65. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/README.md +0 -0
  66. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/docs/index.md +0 -0
  67. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/docs/transaction_rules.md +0 -0
  68. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/mkdocs.yml +0 -0
  69. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/__init__.py +0 -0
  70. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/__init__.py +0 -0
  71. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/rich/__init__.py +0 -0
  72. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/textual/__init__.py +0 -0
  73. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/textual/io.py +0 -0
  74. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/__init__.py +0 -0
  75. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/__init__.py +0 -0
  76. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/accounts/__init__.py +0 -0
  77. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/accounts/create_account.py +0 -0
  78. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/accounts/get_account_by_id.py +0 -0
  79. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/accounts/get_accounts.py +0 -0
  80. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/budgets/__init__.py +0 -0
  81. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/budgets/get_budget_by_id.py +0 -0
  82. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/budgets/get_budget_settings_by_id.py +0 -0
  83. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/budgets/get_budgets.py +0 -0
  84. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/categories/__init__.py +0 -0
  85. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/categories/get_categories.py +0 -0
  86. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/categories/get_category_by_id.py +0 -0
  87. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/categories/get_month_category_by_id.py +0 -0
  88. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/categories/update_category.py +0 -0
  89. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/categories/update_month_category.py +0 -0
  90. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/months/__init__.py +0 -0
  91. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/months/get_budget_month.py +0 -0
  92. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/months/get_budget_months.py +0 -0
  93. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payee_locations/__init__.py +0 -0
  94. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_location_by_id.py +0 -0
  95. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_locations.py +0 -0
  96. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payee_locations/get_payee_locations_by_payee.py +0 -0
  97. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payees/__init__.py +0 -0
  98. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payees/get_payee_by_id.py +0 -0
  99. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payees/get_payees.py +0 -0
  100. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/payees/update_payee.py +0 -0
  101. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/__init__.py +0 -0
  102. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/create_scheduled_transaction.py +0 -0
  103. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/delete_scheduled_transaction.py +0 -0
  104. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/get_scheduled_transaction_by_id.py +0 -0
  105. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/get_scheduled_transactions.py +0 -0
  106. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/scheduled_transactions/update_scheduled_transaction.py +0 -0
  107. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/__init__.py +0 -0
  108. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/create_transaction.py +0 -0
  109. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/delete_transaction.py +0 -0
  110. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/get_transaction_by_id.py +0 -0
  111. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions.py +0 -0
  112. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_account.py +0 -0
  113. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_category.py +0 -0
  114. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_month.py +0 -0
  115. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/get_transactions_by_payee.py +0 -0
  116. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/import_transactions.py +0 -0
  117. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/update_transaction.py +0 -0
  118. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/transactions/update_transactions.py +0 -0
  119. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/user/__init__.py +0 -0
  120. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/api/user/get_user.py +0 -0
  121. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/errors.py +0 -0
  122. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/__init__.py +0 -0
  123. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/account.py +0 -0
  124. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/account_response.py +0 -0
  125. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/account_response_data.py +0 -0
  126. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/account_type.py +0 -0
  127. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/accounts_response.py +0 -0
  128. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/accounts_response_data.py +0 -0
  129. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_detail.py +0 -0
  130. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_detail_response.py +0 -0
  131. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_detail_response_data.py +0 -0
  132. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_settings.py +0 -0
  133. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_settings_response.py +0 -0
  134. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_settings_response_data.py +0 -0
  135. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_summary.py +0 -0
  136. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_summary_response.py +0 -0
  137. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/budget_summary_response_data.py +0 -0
  138. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/bulk_response.py +0 -0
  139. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/bulk_response_data.py +0 -0
  140. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/bulk_response_data_bulk.py +0 -0
  141. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/bulk_transactions.py +0 -0
  142. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/categories_response.py +0 -0
  143. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/categories_response_data.py +0 -0
  144. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category.py +0 -0
  145. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_1.py +0 -0
  146. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_2_type_1.py +0 -0
  147. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_goal_type_type_3_type_1.py +0 -0
  148. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_group.py +0 -0
  149. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_group_with_categories.py +0 -0
  150. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_response.py +0 -0
  151. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/category_response_data.py +0 -0
  152. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/currency_format_type_0.py +0 -0
  153. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/date_format_type_0.py +0 -0
  154. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/error_detail.py +0 -0
  155. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/error_response.py +0 -0
  156. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/existing_transaction.py +0 -0
  157. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/get_transactions_by_account_type.py +0 -0
  158. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/get_transactions_by_category_type.py +0 -0
  159. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/get_transactions_by_month_type.py +0 -0
  160. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/get_transactions_by_payee_type.py +0 -0
  161. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/get_transactions_type.py +0 -0
  162. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/hybrid_transaction.py +0 -0
  163. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/hybrid_transaction_type.py +0 -0
  164. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/hybrid_transactions_response.py +0 -0
  165. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/hybrid_transactions_response_data.py +0 -0
  166. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/loan_account_periodic_value_type_0.py +0 -0
  167. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/month_detail.py +0 -0
  168. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/month_detail_response.py +0 -0
  169. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/month_detail_response_data.py +0 -0
  170. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/month_summaries_response.py +0 -0
  171. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/month_summaries_response_data.py +0 -0
  172. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/month_summary.py +0 -0
  173. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/new_transaction.py +0 -0
  174. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/patch_category_wrapper.py +0 -0
  175. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/patch_month_category_wrapper.py +0 -0
  176. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/patch_payee_wrapper.py +0 -0
  177. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/patch_transactions_wrapper.py +0 -0
  178. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee.py +0 -0
  179. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_location.py +0 -0
  180. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_location_response.py +0 -0
  181. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_location_response_data.py +0 -0
  182. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_locations_response.py +0 -0
  183. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_locations_response_data.py +0 -0
  184. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_response.py +0 -0
  185. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payee_response_data.py +0 -0
  186. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payees_response.py +0 -0
  187. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/payees_response_data.py +0 -0
  188. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/post_account_wrapper.py +0 -0
  189. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/post_scheduled_transaction_wrapper.py +0 -0
  190. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/post_transactions_wrapper.py +0 -0
  191. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/put_scheduled_transaction_wrapper.py +0 -0
  192. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/put_transaction_wrapper.py +0 -0
  193. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_account.py +0 -0
  194. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_category.py +0 -0
  195. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_category_response.py +0 -0
  196. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_category_response_data.py +0 -0
  197. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_month_category.py +0 -0
  198. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_payee.py +0 -0
  199. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_payee_response.py +0 -0
  200. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_payee_response_data.py +0 -0
  201. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_scheduled_transaction.py +0 -0
  202. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_sub_transaction.py +0 -0
  203. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_transaction_with_id_or_import_id.py +0 -0
  204. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_transaction_with_optional_fields.py +0 -0
  205. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_transactions_response.py +0 -0
  206. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/save_transactions_response_data.py +0 -0
  207. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_sub_transaction.py +0 -0
  208. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_detail.py +0 -0
  209. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_frequency.py +0 -0
  210. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_response.py +0 -0
  211. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_response_data.py +0 -0
  212. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_summary.py +0 -0
  213. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transaction_summary_frequency.py +0 -0
  214. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transactions_response.py +0 -0
  215. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/scheduled_transactions_response_data.py +0 -0
  216. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/sub_transaction.py +0 -0
  217. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_cleared_status.py +0 -0
  218. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_detail.py +0 -0
  219. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_1.py +0 -0
  220. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_2_type_1.py +0 -0
  221. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_flag_color_type_3_type_1.py +0 -0
  222. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_response.py +0 -0
  223. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_response_data.py +0 -0
  224. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_summary.py +0 -0
  225. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_summary_debt_transaction_type_type_1.py +0 -0
  226. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_summary_debt_transaction_type_type_2_type_1.py +0 -0
  227. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transaction_summary_debt_transaction_type_type_3_type_1.py +0 -0
  228. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transactions_import_response.py +0 -0
  229. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transactions_import_response_data.py +0 -0
  230. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transactions_response.py +0 -0
  231. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/transactions_response_data.py +0 -0
  232. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/user.py +0 -0
  233. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/user_response.py +0 -0
  234. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/models/user_response_data.py +0 -0
  235. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/adapters/ynab/types.py +0 -0
  236. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/__init__.py +0 -0
  237. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/constants.py +0 -0
  238. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/models/__init__.py +0 -0
  239. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/models/rules.py +0 -0
  240. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/ports/__init__.py +0 -0
  241. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/ports/io.py +0 -0
  242. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/settings.py +0 -0
  243. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/domain/use_cases/__init__.py +0 -0
  244. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/__init__.py +0 -0
  245. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/cli.py +0 -0
  246. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/click/__init__.py +0 -0
  247. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/click/cli.py +0 -0
  248. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/click/commands/__init__.py +0 -0
  249. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/click/commands/rich/__init__.py +0 -0
  250. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/constants.py +0 -0
  251. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/__init__.py +0 -0
  252. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/app.tcss +0 -0
  253. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/__init__.py +0 -0
  254. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/categories/__init__.py +0 -0
  255. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/categories/tabs.py +0 -0
  256. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/common/__init__.py +0 -0
  257. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/common/command_widget.py +0 -0
  258. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/common/dialogs/__init__.py +0 -0
  259. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/common/dialogs/dialog.py +0 -0
  260. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/common/dialogs/form.py +0 -0
  261. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/common/dialogs/screen.py +0 -0
  262. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/payees/__init__.py +0 -0
  263. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/payees/tabs.py +0 -0
  264. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/tabs.py +0 -0
  265. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/transactions/__init__.py +0 -0
  266. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/host/textual/widgets/transactions/tabs.py +0 -0
  267. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/src/ynab_cli/py.typed +0 -0
  268. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/__init__.py +0 -0
  269. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/conftest.py +0 -0
  270. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/factories/__init__.py +0 -0
  271. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/__init__.py +0 -0
  272. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/adapters/__init__.py +0 -0
  273. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/adapters/rich/__init__.py +0 -0
  274. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/adapters/textual/__init__.py +0 -0
  275. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/adapters/textual/test_io.py +0 -0
  276. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/conftest.py +0 -0
  277. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/__init__.py +0 -0
  278. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/models/__init__.py +0 -0
  279. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/models/test_rules.py +0 -0
  280. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/ports/__init__.py +0 -0
  281. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/ports/test_io.py +0 -0
  282. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/domain/use_cases/__init__.py +0 -0
  283. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/__init__.py +0 -0
  284. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/click/__init__.py +0 -0
  285. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/click/commands/__init__.py +0 -0
  286. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/test_cli.py +0 -0
  287. {ynab_cli-0.6.0 → ynab_cli-0.6.2}/tests/unit/host/textual/__init__.py +0 -0
@@ -5,6 +5,22 @@ 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.6.2 (2025-07-09)
9
+
10
+ ### Fix
11
+
12
+ - **tui**: update client token when settings are changed
13
+
14
+ ## v0.6.1 (2025-07-09)
15
+
16
+ ### Fix
17
+
18
+ - fix issue with providing new token
19
+
20
+ ### Refactor
21
+
22
+ - work towards using dependency injection container
23
+
8
24
  ## v0.6.0 (2025-06-28)
9
25
 
10
26
  ### Feat
@@ -10,7 +10,7 @@ lint: .venv # run linters and type checkers
10
10
  uv run mypy .
11
11
 
12
12
  test: .venv lint # run tests with coverage
13
- uv run pytest --cov=src tests
13
+ uv run pytest --cov-report term-missing:skip-covered --cov=src tests
14
14
 
15
15
  clean: # clean up build artifacts and caches
16
16
  rm -f .coverage
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ynab-cli
3
- Version: 0.6.0
3
+ Version: 0.6.2
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/
@@ -24,12 +24,13 @@ Requires-Dist: attrs~=25.3
24
24
  Requires-Dist: click-default-group~=1.2
25
25
  Requires-Dist: click~=8.2
26
26
  Requires-Dist: httpx~=0.28
27
+ Requires-Dist: lagom~=2.7
27
28
  Requires-Dist: python-dotenv~=1.1
28
29
  Requires-Dist: rapidfuzz~=3.13
29
30
  Requires-Dist: rich~=14.0
30
31
  Requires-Dist: rule-engine~=4.5
31
32
  Requires-Dist: textual-fspicker~=0.4
32
- Requires-Dist: textual~=3.5
33
+ Requires-Dist: textual~=3.6
33
34
  Requires-Dist: typing-extensions~=4.14
34
35
  Requires-Dist: uvloop~=0.21
35
36
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ynab-cli"
3
- version = "0.6.0"
3
+ version = "0.6.2"
4
4
  description = "YNAB Command Line Interface"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -38,12 +38,13 @@ dependencies = [
38
38
  "click-default-group~=1.2",
39
39
  "click~=8.2",
40
40
  "httpx~=0.28",
41
+ "lagom~=2.7",
41
42
  "python-dotenv~=1.1",
42
43
  "rapidfuzz~=3.13",
43
44
  "rich~=14.0",
44
45
  "rule-engine~=4.5",
45
46
  "textual-fspicker~=0.4",
46
- "textual~=3.5",
47
+ "textual~=3.6",
47
48
  "typing-extensions~=4.14",
48
49
  "uvloop~=0.21",
49
50
  ]
@@ -6,8 +6,9 @@ from ynab_cli.domain.ports.io import IO, Progress
6
6
 
7
7
 
8
8
  class RichProgress(Progress):
9
- def __init__(self, progress_info: tuple[rich_progress.Progress, rich_progress.TaskID]) -> None:
10
- self._progress, self._task_id = progress_info
9
+ def __init__(self, progress: rich_progress.Progress) -> None:
10
+ self._progress = progress
11
+ self._task_id = progress.add_task("Loading...")
11
12
 
12
13
  @override
13
14
  async def update(
@@ -20,10 +21,9 @@ class RichProgress(Progress):
20
21
 
21
22
 
22
23
  class RichIO(IO):
23
- def __init__(self, progress_info: tuple[rich_progress.Progress, rich_progress.TaskID]) -> None:
24
- progress, _ = progress_info
24
+ def __init__(self, progress: rich_progress.Progress) -> None:
25
25
  self._progress = progress
26
- self.progress = RichProgress(progress_info)
26
+ self.progress = RichProgress(self._progress)
27
27
 
28
28
  @override
29
29
  async def prompt(self, prompt: str, password: bool = False) -> str:
@@ -203,6 +203,16 @@ class AuthenticatedClient:
203
203
  self._async_client.timeout = timeout
204
204
  return evolve(self, timeout=timeout)
205
205
 
206
+ def update_token(self, token: str) -> None:
207
+ """Update the token used for authentication"""
208
+ self.token = token
209
+
210
+ auth_header = f"{self.prefix} {self.token}" if self.prefix else self.token
211
+ if self._client is not None:
212
+ self._client.headers[self.auth_header_name] = auth_header
213
+ if self._async_client is not None:
214
+ self._async_client.headers[self.auth_header_name] = auth_header
215
+
206
216
  def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
207
217
  """Manually set the underlying httpx.Client
208
218
 
@@ -58,7 +58,8 @@ async def run_asyncio_detailed(
58
58
  if not new_access_token:
59
59
  raise e
60
60
 
61
- client.token = new_access_token
61
+ client.update_token(new_access_token)
62
+
62
63
  response = await asyncio_detailed(*args, **kwargs)
63
64
  parsed_data = get_parsed_response_data(response)
64
65
  else:
@@ -0,0 +1,47 @@
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.settings import Settings
9
+
10
+
11
+ class ListAllParams(TypedDict):
12
+ pass
13
+
14
+
15
+ class ListAll:
16
+ """Use case for listing all budgets."""
17
+
18
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
19
+ self._io = io
20
+ self._client = client
21
+
22
+ async def __call__(self, settings: Settings, params: ListAllParams) -> AsyncIterator[models.BudgetSummary]:
23
+ try:
24
+ progress_total = 0
25
+
26
+ budgets = (
27
+ await util.get_asyncio_detailed(
28
+ self._io, get_budgets.asyncio_detailed, include_accounts=False, client=self._client
29
+ )
30
+ ).data.budgets
31
+ budgets.sort(key=lambda b: b.name)
32
+
33
+ progress_total = len(budgets)
34
+ await self._io.progress.update(total=progress_total)
35
+ for budget in budgets:
36
+ await self._io.progress.update(advance=1)
37
+ yield budget
38
+
39
+ except Exception as e:
40
+ if isinstance(e, util.ApiError) and e.status_code == 401:
41
+ await self._io.print("Invalid or expired access token. Please update your settings.")
42
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
43
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
44
+ else:
45
+ await self._io.print(f"Exception when calling YNAB: {e}")
46
+ finally:
47
+ await self._io.progress.update(total=progress_total, completed=progress_total)
@@ -6,7 +6,6 @@ from ynab_cli.adapters.ynab import models, util
6
6
  from ynab_cli.adapters.ynab.api.categories import get_categories
7
7
  from ynab_cli.adapters.ynab.api.transactions import get_transactions_by_category
8
8
  from ynab_cli.domain import ports
9
- from ynab_cli.domain.constants import YNAB_API_URL
10
9
  from ynab_cli.domain.settings import Settings
11
10
 
12
11
 
@@ -20,47 +19,48 @@ class ListUnusedParams(TypedDict):
20
19
  pass
21
20
 
22
21
 
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
22
+ class ListUnused:
23
+ """Use case for listing unused categories."""
28
24
 
29
- if client is None:
30
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
25
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
26
+ self._io = io
27
+ self._client = client
28
+
29
+ async def __call__(self, settings: Settings, params: ListUnusedParams) -> AsyncIterator[models.Category]:
30
+ try:
31
+ progress_total = 0
31
32
 
32
- async with client:
33
33
  category_groups = (
34
34
  await util.get_asyncio_detailed(
35
- io, get_categories.asyncio_detailed, settings.ynab.budget_id, client=client
35
+ self._io, get_categories.asyncio_detailed, settings.ynab.budget_id, client=self._client
36
36
  )
37
37
  ).data.category_groups
38
38
  category_groups.sort(key=lambda cg: cg.name)
39
39
 
40
40
  progress_total = len(category_groups)
41
- await io.progress.update(total=progress_total)
41
+ await self._io.progress.update(total=progress_total)
42
42
  for category_group in category_groups:
43
- await io.progress.update(advance=1)
43
+ await self._io.progress.update(advance=1)
44
44
 
45
45
  if _should_skip_category_or_group(category_or_group=category_group):
46
46
  continue
47
47
 
48
48
  progress_total += len(category_group.categories)
49
- await io.progress.update(total=progress_total)
49
+ await self._io.progress.update(total=progress_total)
50
50
  category_group.categories.sort(key=lambda c: c.name)
51
51
  for category in category_group.categories:
52
- await io.progress.update(advance=1)
52
+ await self._io.progress.update(advance=1)
53
53
 
54
54
  if _should_skip_category_or_group(category_or_group=category):
55
55
  continue
56
56
 
57
57
  transactions = (
58
58
  await util.get_asyncio_detailed(
59
- io,
59
+ self._io,
60
60
  get_transactions_by_category.asyncio_detailed,
61
61
  settings.ynab.budget_id,
62
62
  str(category.id),
63
- client=client,
63
+ client=self._client,
64
64
  )
65
65
  ).data.transactions
66
66
  num_transactions = len(transactions)
@@ -69,63 +69,64 @@ async def list_unused(
69
69
  if not num_transactions:
70
70
  yield category
71
71
 
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}")
79
- finally:
80
- await io.progress.update(total=progress_total, completed=progress_total)
72
+ except Exception as e:
73
+ if isinstance(e, util.ApiError) and e.status_code == 401:
74
+ await self._io.print("Invalid or expired access token. Please update your settings.")
75
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
76
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
77
+ else:
78
+ await self._io.print(f"Exception when calling YNAB: {e}")
79
+ finally:
80
+ await self._io.progress.update(total=progress_total, completed=progress_total)
81
81
 
82
82
 
83
83
  class ListAllParams(TypedDict):
84
84
  pass
85
85
 
86
86
 
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
87
+ class ListAll:
88
+ """Use case for listing all categories."""
89
+
90
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
91
+ self._io = io
92
+ self._client = client
92
93
 
93
- if client is None:
94
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
94
+ async def __call__(self, settings: Settings, params: ListAllParams) -> AsyncIterator[models.Category]:
95
+ try:
96
+ progress_total = 0
95
97
 
96
- async with client:
97
98
  category_groups = (
98
99
  await util.get_asyncio_detailed(
99
- io, get_categories.asyncio_detailed, settings.ynab.budget_id, client=client
100
+ self._io, get_categories.asyncio_detailed, settings.ynab.budget_id, client=self._client
100
101
  )
101
102
  ).data.category_groups
102
103
  category_groups.sort(key=lambda cg: cg.name)
103
104
 
104
105
  progress_total = len(category_groups)
105
- await io.progress.update(total=progress_total)
106
+ await self._io.progress.update(total=progress_total)
106
107
  for category_group in category_groups:
107
- await io.progress.update(advance=1)
108
+ await self._io.progress.update(advance=1)
108
109
 
109
110
  if _should_skip_category_or_group(category_or_group=category_group):
110
111
  continue
111
112
 
112
113
  progress_total += len(category_group.categories)
113
- await io.progress.update(total=progress_total)
114
+ await self._io.progress.update(total=progress_total)
114
115
  category_group.categories.sort(key=lambda c: c.name)
115
116
  for category in category_group.categories:
116
- await io.progress.update(advance=1)
117
+ await self._io.progress.update(advance=1)
117
118
 
118
119
  if _should_skip_category_or_group(category_or_group=category):
119
120
  continue
120
121
 
121
122
  yield category
122
123
 
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}")
130
- finally:
131
- await io.progress.update(total=progress_total, completed=progress_total)
124
+ except Exception as e:
125
+ if isinstance(e, util.ApiError) and e.status_code == 401:
126
+ await self._io.print("Invalid or expired access token. Please update your settings.")
127
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
128
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
129
+ else:
130
+ await self._io.print(f"Exception when calling YNAB: {e}")
131
+ finally:
132
+ await self._io.progress.update(total=progress_total, completed=progress_total)
@@ -10,7 +10,7 @@ from ynab_cli.adapters.ynab import models, util
10
10
  from ynab_cli.adapters.ynab.api.payees import get_payees, update_payee
11
11
  from ynab_cli.adapters.ynab.api.transactions import get_transactions_by_payee
12
12
  from ynab_cli.domain import ports
13
- from ynab_cli.domain.constants import UNUSED_PREFIX, YNAB_API_URL
13
+ from ynab_cli.domain.constants import UNUSED_PREFIX
14
14
  from ynab_cli.domain.settings import Settings
15
15
 
16
16
 
@@ -44,25 +44,30 @@ class NormalizeNamesParams(TypedDict):
44
44
  pass
45
45
 
46
46
 
47
- async def normalize_names(
48
- settings: Settings, io: ports.IO, params: NormalizeNamesParams, *, client: ynab.AuthenticatedClient | None = None
49
- ) -> AsyncIterator[tuple[models.Payee, str]]:
50
- try:
51
- progress_total = 0
47
+ class NormalizeNames:
48
+ """Use case for normalizing payee names."""
52
49
 
53
- if client is None:
54
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
50
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
51
+ self._io = io
52
+ self._client = client
53
+
54
+ async def __call__(
55
+ self, settings: Settings, params: NormalizeNamesParams
56
+ ) -> AsyncIterator[tuple[models.Payee, str]]:
57
+ try:
58
+ progress_total = 0
55
59
 
56
- async with client:
57
60
  payees = (
58
- await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
61
+ await util.get_asyncio_detailed(
62
+ self._io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=self._client
63
+ )
59
64
  ).data.payees
60
65
  payees.sort(key=lambda p: p.name)
61
66
 
62
67
  progress_total = len(payees)
63
- await io.progress.update(total=progress_total)
68
+ await self._io.progress.update(total=progress_total)
64
69
  for payee in payees:
65
- await io.progress.update(advance=1)
70
+ await self._io.progress.update(advance=1)
66
71
 
67
72
  if _should_skip_payee(payee=payee):
68
73
  continue
@@ -73,50 +78,55 @@ async def normalize_names(
73
78
 
74
79
  if not settings.dry_run:
75
80
  await util.run_asyncio_detailed(
76
- io,
81
+ self._io,
77
82
  update_payee.asyncio_detailed,
78
83
  settings.ynab.budget_id,
79
84
  str(payee.id),
80
- client=client,
85
+ client=self._client,
81
86
  body=models.PatchPayeeWrapper(payee=models.SavePayee(name=normalized_name)),
82
87
  )
83
88
 
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}")
91
- finally:
92
- await io.progress.update(total=progress_total, completed=progress_total)
89
+ except Exception as e:
90
+ if isinstance(e, util.ApiError) and e.status_code == 401:
91
+ await self._io.print("Invalid or expired access token. Please update your settings.")
92
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
93
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
94
+ else:
95
+ await self._io.print(f"Exception when calling YNAB: {e}")
96
+ finally:
97
+ await self._io.progress.update(total=progress_total, completed=progress_total)
93
98
 
94
99
 
95
100
  class ListDuplicatesParams(TypedDict):
96
101
  pass
97
102
 
98
103
 
99
- async def list_duplicates(
100
- settings: Settings, io: ports.IO, params: ListDuplicatesParams, *, client: ynab.AuthenticatedClient | None = None
101
- ) -> AsyncIterator[tuple[models.Payee, models.Payee]]:
102
- try:
103
- progress_total = 0
104
+ class ListDuplicates:
105
+ """Use case for listing duplicate payees."""
106
+
107
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
108
+ self._io = io
109
+ self._client = client
104
110
 
105
- if client is None:
106
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
111
+ async def __call__(
112
+ self, settings: Settings, params: ListDuplicatesParams
113
+ ) -> AsyncIterator[tuple[models.Payee, models.Payee]]:
114
+ try:
115
+ progress_total = 0
107
116
 
108
- async with client:
109
117
  possible_duplicates: dict[tuple[UUID, str], list[tuple[UUID, str]]] = {}
110
118
 
111
119
  payees = (
112
- await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
120
+ await util.get_asyncio_detailed(
121
+ self._io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=self._client
122
+ )
113
123
  ).data.payees
114
124
  payees.sort(key=lambda p: p.name)
115
125
 
116
126
  progress_total = len(payees)
117
- await io.progress.update(total=progress_total)
127
+ await self._io.progress.update(total=progress_total)
118
128
  for idx, payee in enumerate(payees):
119
- await io.progress.update(advance=1)
129
+ await self._io.progress.update(advance=1)
120
130
 
121
131
  if _should_skip_payee(payee=payee):
122
132
  continue
@@ -144,51 +154,54 @@ async def list_duplicates(
144
154
 
145
155
  yield (payee, filtered_payee)
146
156
 
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}")
154
- finally:
155
- await io.progress.update(total=progress_total, completed=progress_total)
157
+ except Exception as e:
158
+ if isinstance(e, util.ApiError) and e.status_code == 401:
159
+ await self._io.print("Invalid or expired access token. Please update your settings.")
160
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
161
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
162
+ else:
163
+ await self._io.print(f"Exception when calling YNAB: {e}")
164
+ finally:
165
+ await self._io.progress.update(total=progress_total, completed=progress_total)
156
166
 
157
167
 
158
168
  class ListUnusedParams(TypedDict):
159
169
  prefix_unused: bool
160
170
 
161
171
 
162
- async def list_unused(
163
- settings: Settings, io: ports.IO, params: ListUnusedParams, *, client: ynab.AuthenticatedClient | None = None
164
- ) -> AsyncIterator[models.Payee]:
165
- try:
166
- progress_total = 0
172
+ class ListUnused:
173
+ """Use case for listing unused payees."""
167
174
 
168
- if client is None:
169
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
175
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
176
+ self._io = io
177
+ self._client = client
178
+
179
+ async def __call__(self, settings: Settings, params: ListUnusedParams) -> AsyncIterator[models.Payee]:
180
+ try:
181
+ progress_total = 0
170
182
 
171
- async with client:
172
183
  payees = (
173
- await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
184
+ await util.get_asyncio_detailed(
185
+ self._io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=self._client
186
+ )
174
187
  ).data.payees
175
188
  payees.sort(key=lambda p: p.name)
176
189
 
177
190
  progress_total = len(payees)
178
- await io.progress.update(total=progress_total)
191
+ await self._io.progress.update(total=progress_total)
179
192
  for payee in payees:
180
- await io.progress.update(advance=1)
193
+ await self._io.progress.update(advance=1)
181
194
 
182
195
  if _should_skip_payee(payee=payee):
183
196
  continue
184
197
 
185
198
  transactions = (
186
199
  await util.get_asyncio_detailed(
187
- io,
200
+ self._io,
188
201
  get_transactions_by_payee.asyncio_detailed,
189
202
  settings.ynab.budget_id,
190
203
  str(payee.id),
191
- client=client,
204
+ client=self._client,
192
205
  )
193
206
  ).data.transactions
194
207
  num_transactions = len(transactions)
@@ -201,60 +214,63 @@ async def list_unused(
201
214
  if not settings.dry_run and params.get("prefix_unused", False):
202
215
  new_name = f"{UNUSED_PREFIX} {payee.name}"
203
216
  await util.run_asyncio_detailed(
204
- io,
217
+ self._io,
205
218
  update_payee.asyncio_detailed,
206
219
  settings.ynab.budget_id,
207
220
  str(payee.id),
208
- client=client,
221
+ client=self._client,
209
222
  body=models.PatchPayeeWrapper(payee=models.SavePayee(name=new_name)),
210
223
  )
211
224
 
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}")
219
- finally:
220
- await io.progress.update(total=progress_total, completed=progress_total)
225
+ except Exception as e:
226
+ if isinstance(e, util.ApiError) and e.status_code == 401:
227
+ await self._io.print("Invalid or expired access token. Please update your settings.")
228
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
229
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
230
+ else:
231
+ await self._io.print(f"Exception when calling YNAB: {e}")
232
+ finally:
233
+ await self._io.progress.update(total=progress_total, completed=progress_total)
221
234
 
222
235
 
223
236
  class ListAllParams(TypedDict):
224
237
  pass
225
238
 
226
239
 
227
- async def list_all(
228
- settings: Settings, io: ports.IO, params: ListAllParams, *, client: ynab.AuthenticatedClient | None = None
229
- ) -> AsyncIterator[models.Payee]:
230
- try:
231
- progress_total = 0
240
+ class ListAll:
241
+ """Use case for listing all payees."""
242
+
243
+ def __init__(self, io: ports.IO, client: ynab.AuthenticatedClient):
244
+ self._io = io
245
+ self._client = client
232
246
 
233
- if client is None:
234
- client = ynab.AuthenticatedClient(base_url=YNAB_API_URL, token=settings.ynab.access_token)
247
+ async def __call__(self, settings: Settings, params: ListAllParams) -> AsyncIterator[models.Payee]:
248
+ try:
249
+ progress_total = 0
235
250
 
236
- async with client:
237
251
  payees = (
238
- await util.get_asyncio_detailed(io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=client)
252
+ await util.get_asyncio_detailed(
253
+ self._io, get_payees.asyncio_detailed, settings.ynab.budget_id, client=self._client
254
+ )
239
255
  ).data.payees
240
256
  payees.sort(key=lambda p: p.name)
241
257
 
242
258
  progress_total = len(payees)
243
- await io.progress.update(total=progress_total)
259
+ await self._io.progress.update(total=progress_total)
244
260
  for payee in payees:
245
- await io.progress.update(advance=1)
261
+ await self._io.progress.update(advance=1)
246
262
 
247
263
  if _should_skip_payee(payee=payee):
248
264
  continue
249
265
 
250
266
  yield payee
251
267
 
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}")
259
- finally:
260
- await io.progress.update(total=progress_total, completed=progress_total)
268
+ except Exception as e:
269
+ if isinstance(e, util.ApiError) and e.status_code == 401:
270
+ await self._io.print("Invalid or expired access token. Please update your settings.")
271
+ elif isinstance(e, util.ApiError) and e.status_code == 429:
272
+ await self._io.print("API rate limit exceeded. Try again later, or get a new access token.")
273
+ else:
274
+ await self._io.print(f"Exception when calling YNAB: {e}")
275
+ finally:
276
+ await self._io.progress.update(total=progress_total, completed=progress_total)