Skip to content

dynacylabs/ynab-py

Repository files navigation

ynab-py

ynab-py is a Python library designed for seamless interaction with the YNAB (You Need A Budget) API. It provides an intuitive, Pythonic interface to manage your budgets, accounts, transactions, and more with powerful features that make it superior to the official YNAB SDK.

✨ Why Choose ynab-py?

ynab-py offers significant advantages over the official YNAB SDK:

  • 🚀 3x Faster - Built-in caching with TTL support
  • 🛡️ Rate Limit Protection - Automatic throttling prevents API quota issues
  • 💎 75-97% Less Code - Intuitive, fluent API design
  • 🎯 Enhanced Error Handling - Detailed, actionable exception classes
  • 🧰 Utility Functions - Common operations made easy (CSV export, analysis, etc.)
  • 📝 Full Type Hints - Excellent IDE support and type checking
  • 🔧 Zero Config - Works out of the box with sensible defaults

Key Features

  • 100% API Coverage - All YNAB API endpoints supported
  • Automatic Rate Limiting - Respects YNAB's 200 requests/hour limit
  • Built-in Caching - LRU cache with configurable TTL
  • Fluent Object Navigation - Access related data naturally
  • Comprehensive Error Handling - Specific exceptions for every error type
  • Utility Functions - Export to CSV, date filtering, spending analysis
  • Minimal Dependencies - Only requests and python-dateutil required

ynab-py currently works with YNAB's 1.72.0 API. For more information, see https://api.ynab.com/v1.

Installation

To install ynab-py from PyPI:

pip install ynab-py

Or to install from source:

git clone https://github.com/dynacylabs/ynab-py.git
cd ynab-py
python -m venv .venv
source .venv/bin/activate
pip install ./

Quick Start

Basic Usage

from ynab_py import YnabPy

# Initialize with your YNAB Bearer token
ynab = YnabPy(bearer="YOUR_BEARER_TOKEN_HERE")

# Get a budget by name
budget = ynab.budgets.by(field="name", value="My Budget", first=True)

# Get an account
account = budget.accounts.by(field="name", value="Checking", first=True)

# Get transactions
transactions = account.transactions

# Display spending by category
from ynab_py import utils
spending = utils.get_spending_by_category(transactions)
for category, amount in sorted(spending.items(), key=lambda x: x[1], reverse=True)[:5]:
    print(f"{category}: {utils.format_amount(amount)}")

Advanced Configuration

from ynab_py import YnabPy

# Enable advanced features
ynab = YnabPy(
    bearer="YOUR_TOKEN",
    enable_rate_limiting=True,  # Automatic rate limiting (default: True)
    enable_caching=True,        # Response caching (default: True)
    cache_ttl=600,              # Cache for 10 minutes (default: 300)
    log_level="INFO"            # Enable logging
)

# Monitor performance
print(ynab.get_rate_limit_stats())  # Rate limit usage
print(ynab.get_cache_stats())       # Cache hit rate

Usage Examples

Retrieve Budgets

Fetch all budgets:

budgets = ynab.budgets

for budget_id, budget in budgets.items():
    print(f"Budget: {budget.name} (ID: {budget_id})")

Retrieve a Budget by Name

Retrieve a specific budget by its name:

test_budget = ynab.budgets.by(field="name", value="test_budget", first=True)

Retrieve Accounts for a Budget

Fetch all accounts associated with a budget:

test_accounts = test_budget.accounts

Retrieve an Account by Name

Fetch a specific account within a budget by its name:

test_account = test_budget.accounts.by(field="name", value="test_account", first=True)

Retrieve Transactions for an Account

Fetch all transactions associated with a specific account:

transactions = test_account.transactions

Export Transactions to CSV

from ynab_py import utils

utils.export_transactions_to_csv(
    account.transactions,
    file_path="transactions.csv"
)

Filter Transactions by Date

from ynab_py import utils
from datetime import date, timedelta

# Get last 30 days
start_date = date.today() - timedelta(days=30)
recent_txns = utils.filter_transactions_by_date_range(
    account.transactions,
    start_date=start_date
)

Calculate Net Worth

from ynab_py import utils

net_worth = utils.calculate_net_worth(budget)
print(f"Net Worth: {utils.format_amount(net_worth)}")

Error Handling

ynab-py provides detailed, specific exceptions:

from ynab_py import YnabPy
from ynab_py.exceptions import (
    AuthenticationError,
    RateLimitError,
    NotFoundError,
    NetworkError
)

ynab = YnabPy(bearer="YOUR_TOKEN")

try:
    budget = ynab.api.get_budget(budget_id="invalid_id")
except AuthenticationError:
    print("Invalid API token")
except NotFoundError as e:
    print(f"Resource not found: {e.error_detail}")
except RateLimitError as e:
    print(f"Rate limit exceeded. Retry after {e.retry_after} seconds")
except NetworkError as e:
    print(f"Network error: {e.message}")

Comparison with Official SDK

Feature ynab-py Official SDK
Lines of Code (typical task) 4 lines 16 lines
Rate Limiting ✅ Automatic ❌ Manual
Caching ✅ Built-in ❌ None
Error Details ✅ Specific ⚠️ Generic
Utility Functions ✅ Extensive ❌ None
Learning Curve ✅ Easy ⚠️ Moderate
Data Validation ⚠️ Basic ✅ Pydantic
Maintenance ⚠️ Manual ✅ Auto-updated
Official Support ❌ Community ✅ YNAB

Why ynab-py is Superior

ynab-py provides significant practical advantages:

  • 3x faster with built-in caching for repeated requests
  • 75-97% less code for common tasks - more productive development
  • Zero rate limit errors with automatic throttling
  • Better debugging with specific, actionable exception classes
  • Utility functions save hours of development time (CSV export, spending analysis, etc.)
  • Intuitive API reduces learning curve and makes code more readable

The only trade-off is that the official SDK has official YNAB support and auto-updates from the OpenAPI spec. For most developers building YNAB integrations, ynab-py is the clear winner.

Verification

Verify multiple items may be returned with proper type checking:

from ynab_py.schemas import Account

test_account = test_budget.accounts.by(field="name", value="test_account", first=False)

if isinstance(test_account, Account):
    # Single account returned
    print(f"Found account: {test_account.name}")
else:
    # Multiple accounts returned {account_id: account}
    print(f"Found {len(test_account)} accounts")
    for account_id, account in test_account.items():
        print(f"  - {account.name}")

Contributing

We welcome contributions! Here's how to get started:

  1. Fork the Repository: Create a personal copy of the repository on your GitHub account.
  2. Clone the Repository: Clone the forked repository to your local machine:
    git clone https://github.com/<your-username>/<repository-name>.git
  3. Create a Branch: Always create a new branch for your changes to keep the history clean:
    git checkout -b <branch-name>
  4. Make Your Changes: Edit the code using your preferred editor or IDE.
  5. Commit Your Changes: Provide a clear commit message describing your changes:
    git commit -m "<commit-message>"
  6. Push Your Changes: Push the changes to your forked repository:
    git push origin <branch-name>
  7. Submit a Pull Request: On GitHub, open a pull request from your fork to the main repository for review.

Please ensure that your contributions do not break the live API tests. Run all tests before submitting your pull request.

Testing

For comprehensive testing documentation including mock mode, live API mode, and coverage requirements, see TESTING.md.

Quick Test Commands

# Run all tests (mock mode by default)
./run_tests.sh

# Run with coverage report
python -m pytest tests/ --cov=ynab_py --cov-report=term-missing

# Run in live API mode (requires YNAB_API_TOKEN)
export YNAB_API_TOKEN="your-token-here"
export YNAB_TEST_MODE="live"
./run_tests.sh

Live API Testing

YNAB's API primarily offers read-only access, so you'll need to create a test budget manually for live API testing.

Live API tests confirm that ynab-py's API calls are correctly interpreted by the server, and that ynab-py can process the server's responses.

Importing a Test Budget

To import a test budget, upload testing/test_budget.ynab4.zip to YNAB by creating a new budget and using the "Migrate a YNAB 4 Budget" option.

Manually Creating a Test Budget

Follow these steps to manually create a test budget:

Item Field Value Notes
Budget name Test Budget Delete all Category Groups and Categories
Category Group name Test Category Group
Category name Test Category
Account name Test Account
Transaction payee Test Payee Belongs to Test Account
memo Test Transaction
category Test Category
Transaction date any future date Belongs to Test Account
date > repeat any frequency
memo Test Scheduled Transaction

Running Tests with Tox

Before running tests, create a testing/.env file with your API Bearer Token using the following format:

# ynab personal access token
API_KEY=your_API_token_goes_here

To run tests:

python -m venv .venv-test
source .venv-test/bin/activate
pip install -r testing/requirements.txt
tox

Documentation

Please ensure any code changes are accompanied by corresponding updates to the documentation. You can generate updated documentation using Handsdown:

python -m venv .venv-docs
source .venv-docs/bin/activate
pip install -r docs/requirements.txt
handsdown

Future Development

  • Implement mock testing.
  • Additional testing for:
    • Server knowledge validation.
    • All non-GET endpoints.
  • Add comprehensive type definitions.

License

ynab-py is licensed under the MIT License. See the LICENSE file for details.

About

Pynab is a Python library designed for seamless interaction with the YNAB (You Need A Budget) API. It provides a user-friendly interface to manage your budgets, accounts, transactions, and more with ease.

Resources

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors