Skip to main content

Python client for the Fizzy API

Project description

Fizzy API Client

Python client for the Fizzy API.

Tests PyPI version Python 3.10+ License: MIT

Installation

pip install fizzy-api-client

Quick Start

from fizzy import FizzyClient

client = FizzyClient(
    token="your-api-token",
    account_slug="your-account-slug"
)

# List boards
boards = client.boards.list()

# Create a card
card = client.cards.create(
    board_id="board-123",
    title="My new card"
)

# List cards with filtering
cards = client.cards.list(
    board_id="board-123",
    tag_ids=["tag-1", "tag-2"],
    status="open"
)

Authentication

Personal Access Token

The recommended authentication method for scripts and integrations:

from fizzy import FizzyClient

client = FizzyClient(
    token="your-personal-access-token",
    account_slug="your-account-slug"
)

Magic Link Session

For native applications that need user authentication:

from fizzy import FizzyClient
from fizzy.auth import request_magic_link, submit_magic_code

# Request a magic link
request_magic_link("user@example.com")

# User receives email with 6-character code
session_token = submit_magic_code("ABC123")

# Use the session token
client = FizzyClient(
    session_token=session_token,
    account_slug="your-account-slug"
)

Resources

Identity

# Get authenticated user info (list of accounts)
identity = client.identity.get()
for account in identity.accounts:
    print(f"Account: {account.name} ({account.id})")
    print(f"  User: {account.user.name} - Role: {account.user.role}")

Boards

# List all boards
boards = client.boards.list()

# Get a specific board
board = client.boards.get("board-id")

# Create a board
board = client.boards.create(
    name="My Board",
    public_description="<p>Optional rich text description</p>"
)

# Update a board
board = client.boards.update("board-id", name="New Name")

# Delete a board
client.boards.delete("board-id")

Cards

# List cards with optional filters
cards = client.cards.list(
    board_id="board-123",     # Optional: filter by board
    column_id="col-456",      # Optional: filter by column
    tag_ids=["tag-1"],        # Optional: filter by tags
    assignee_ids=["user-1"],  # Optional: filter by assignees
    status="open"             # Optional: "open", "closed", "deferred"
)

# Get a specific card by number
card = client.cards.get(42)  # Cards are accessed by number, not ID

# Create a card
card = client.cards.create(
    board_id="board-123",
    title="New Card",
    description="<p>Rich text description</p>"  # HTML supported
)

# Create a card with header image
card = client.cards.create(
    board_id="board-123",
    title="Card with Image",
    image="/path/to/image.png"  # File path or tuple (filename, file_obj, content_type)
)

# Update a card
card = client.cards.update(42, title="Updated Title")

# Update card with new header image
card = client.cards.update(42, image="/path/to/new-image.png")

# Delete card header image
client.cards.delete_image(42)

# Delete a card
client.cards.delete(42)

# Card operations
client.cards.close(42)                          # Close the card
client.cards.reopen(42)                         # Reopen a closed card
client.cards.postpone(42)                       # Move to "not now"
client.cards.triage(42, column_id="col-123")    # Move to a column
client.cards.untriage(42)                       # Remove from triage
client.cards.toggle_tag(42, tag_title="Bug")    # Toggle a tag on/off
client.cards.toggle_assignment(42, assignee_id="user-123")  # Toggle assignment
client.cards.watch(42)                          # Start watching
client.cards.unwatch(42)                        # Stop watching
client.cards.gild(42)                           # Make a "golden ticket"
client.cards.ungild(42)                         # Remove golden status

Comments

# List comments on a card
comments = client.comments.list(42)  # card_number

# Get a specific comment
comment = client.comments.get(42, "comment-id")

# Create a comment
comment = client.comments.create(
    42,  # card_number
    body="<p>Hello, world!</p>"  # HTML supported
)

# Update a comment
comment = client.comments.update(42, "comment-id", body="<p>Updated</p>")

# Delete a comment
client.comments.delete(42, "comment-id")

Reactions

# List reactions on a comment
reactions = client.reactions.list(42, "comment-id")

# Add a reaction
reaction = client.reactions.create(42, "comment-id", content="thumbs_up")

# Remove a reaction
client.reactions.delete(42, "comment-id", "reaction-id")

Steps (Checklist Items)

# List steps (retrieved from the card)
steps = client.steps.list(42)  # card_number

# Get a specific step
step = client.steps.get(42, "step-id")

# Create a step
step = client.steps.create(42, content="Do this thing")

# Update a step (mark complete)
client.steps.update(42, "step-id", completed=True)

# Delete a step
client.steps.delete(42, "step-id")

Columns

# List columns on a board
columns = client.columns.list("board-id")

# Get a specific column
column = client.columns.get("board-id", "column-id")

# Create a column
column = client.columns.create("board-id", name="In Progress")

# Update a column
column = client.columns.update("board-id", "column-id", name="Done")

# Delete a column
client.columns.delete("board-id", "column-id")

Users

# List users in account
users = client.users.list()

# Get a specific user
user = client.users.get("user-id")

# Update a user (name or avatar)
user = client.users.update("user-id", name="New Name")
user = client.users.update("user-id", avatar="/path/to/avatar.png")

# Deactivate a user
client.users.delete("user-id")

Tags

# List all tags in account
tags = client.tags.list()

Notifications

# List notifications
notifications = client.notifications.list()

# Filter by read status
unread = client.notifications.list(read=False)

# Mark as read
client.notifications.mark_read("notification-id")

# Mark as unread
client.notifications.mark_unread("notification-id")

# Bulk mark as read
client.notifications.bulk_mark_read(["notif-1", "notif-2", "notif-3"])

File Uploads (Direct Uploads for Rich Text)

For embedding images in card descriptions or comments:

import hashlib
import base64

# Calculate MD5 checksum
with open("image.png", "rb") as f:
    content = f.read()
    checksum = base64.b64encode(hashlib.md5(content).digest()).decode()

# Create a direct upload
upload = client.uploads.create_direct_upload(
    filename="image.png",
    content_type="image/png",
    byte_size=len(content),
    checksum=checksum
)

# Upload to storage using the provided URL and headers
import httpx
httpx.put(
    upload.upload_url,
    content=content,
    headers=upload.upload_headers
)

# Build an ActionText attachment tag
from fizzy import DirectUpload
tag = DirectUpload.build_attachment_tag(upload.signed_id)

# Use in card description or comment
client.cards.update(42, description=f"<p>Check this out: {tag}</p>")
client.comments.create(42, body=f"<p>Here's an image: {tag}</p>")

Pagination

# Auto-pagination iterator
for card in client.cards.list_all(board_id="board-123"):
    print(card.title)

# Manual pagination
page = client.cards.list_paginated(board_id="board-123")
while page.has_next:
    for card in page.items:
        print(card.title)
    page = page.next_page()

Async Usage

from fizzy import AsyncFizzyClient
import asyncio

async def main():
    async with AsyncFizzyClient(
        token="your-token",
        account_slug="your-account-slug"
    ) as client:
        # All methods are async
        boards = await client.boards.list()

        # Async iteration
        async for card in client.cards.list_all():
            print(card.title)

asyncio.run(main())

Error Handling

from fizzy.exceptions import (
    FizzyError,           # Base exception
    AuthenticationError,  # 401
    ForbiddenError,       # 403
    NotFoundError,        # 404
    BadRequestError,      # 400
    RateLimitError,       # 429
    ServerError,          # 5xx
)

try:
    card = client.cards.get(99999)
except NotFoundError as e:
    print(f"Card not found: {e.message}")
except BadRequestError as e:
    print(f"Bad request: {e.message}")
except FizzyError as e:
    print(f"API error: {e.status_code} - {e.message}")

Caching

ETag caching is enabled by default for GET requests:

# First request stores the ETag
card = client.cards.get(123)

# Subsequent requests use If-None-Match header
# Returns cached response if server returns 304
card = client.cards.get(123)

# Disable caching
client = FizzyClient(
    token="...",
    account_slug="...",
    cache=False
)

Configuration

client = FizzyClient(
    token="your-token",
    account_slug="your-account-slug",
    base_url="https://app.fizzy.do",  # Default
    cache=True,                        # Enable ETag caching (default)
    timeout=30.0,                      # Request timeout in seconds
)

Development

# Clone the repository
git clone https://github.com/robzolkos/fizzy-client-python.git
cd fizzy-client-python

# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=src/fizzy --cov-report=html

# Run linting
ruff check src tests
ruff format --check src tests

# Run type checking
mypy src

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see the LICENSE file for details.


Fizzy is a product and trademark of 37signals.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fizzy_api_client-1.0.0.tar.gz (22.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fizzy_api_client-1.0.0-py3-none-any.whl (35.4 kB view details)

Uploaded Python 3

File details

Details for the file fizzy_api_client-1.0.0.tar.gz.

File metadata

  • Download URL: fizzy_api_client-1.0.0.tar.gz
  • Upload date:
  • Size: 22.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fizzy_api_client-1.0.0.tar.gz
Algorithm Hash digest
SHA256 cb4f33de4684a51679a5dbabd603b1242af98bdd216bb5f861d2c4581d93550b
MD5 8822c3d1e1f3c93b6d26cf0d2353a8a6
BLAKE2b-256 48e78ab4aee55a60d2089598c2406754ce60219688832ecb87d228432c6ffa6f

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzy_api_client-1.0.0.tar.gz:

Publisher: release.yml on robzolkos/fizzy-client-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fizzy_api_client-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for fizzy_api_client-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 347a3c356b160e561406fc06d46d97a347dfd359b77ba993e76511396a019f35
MD5 dd641c7575d12a101c02f524a10ffd9d
BLAKE2b-256 456d945a1e56aff364e1db9dbd79df752690e852d690ac2e1fff6deb714fca7d

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzy_api_client-1.0.0-py3-none-any.whl:

Publisher: release.yml on robzolkos/fizzy-client-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page