Skip to content

Item Attachments

This example shows how to upload, list, and download file attachments on a Homebox item.

What it demonstrates

  • Creating a temporary location and item
  • Uploading a text file attachment (type="manual") with client.items.create_item_attachment()
  • Uploading a photo attachment (type="photo") with client.items.create_item_attachment()
  • Retrieving attachment metadata from client.items.get_item()
  • Downloading raw attachment bytes with client.items.get_item_attachment()

Setup

Copy examples/.env.sample to examples/.env and set:

HOMEBOX_URL=https://your-homebox-instance/api
HOMEBOX_USERNAME=your@email.com
HOMEBOX_PASSWORD=yourpassword

Then run:

python examples/item_attachements.py

The downloaded attachment will be saved to the current working directory.

Source code

examples/item_attachements.py
"""Example script that shows how to attach files to items in Homebox.

Script creates a new location, then creates a new item in that location and attaches two files to that item.
Exemplifies also how to retrieve the list of attachments for an item, and how to download an attachment.

In order to run this script, you need to have the following environment variables set:
- HOMEBOX_URL: the URL of your Homebox instance (e.g. http://localhost
- HOMEBOX_USERNAME: the username of a user with permissions to create locations and items
- HOMEBOX_PASSWORD: the password of that user

You can use the .env.sample file in the examples directory as a template for your .env file.
"""

from __future__ import annotations

import base64
import os
from datetime import UTC, datetime
from pathlib import Path

import requests

from homebox import HomeboxClient
from homebox.models import ItemAttachmentUpdate, ItemCreate, LocationCreate


def _load_dotenv() -> None:
    dotenv_path = Path(__file__).parent / ".env"
    if not dotenv_path.is_file():
        return

    with dotenv_path.open() as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                continue
            key, value = line.split("=", 1)
            os.environ.setdefault(key.strip(), value.strip())


def _require_env(name: str) -> str:
    value = os.getenv(name)
    if not value:
        raise RuntimeError(f"Missing required environment variable: {name}")
    return value


def _build_client() -> HomeboxClient:
    base_url = _require_env("HOMEBOX_URL")
    username = _require_env("HOMEBOX_USERNAME")
    password = _require_env("HOMEBOX_PASSWORD")

    client = HomeboxClient(base_url=base_url)
    client.login(username, password)
    return client


def _tiny_png() -> bytes:
    return base64.b64decode(
        "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7+X0kAAAAASUVORK5CYII="
    )


def _download_attachment(
    base_url: str,
    item_id: str,
    attachment_id: str,
    token: str,
    destination: Path,
) -> bool:
    base = base_url.rstrip("/")
    urls = [
        f"{base}/v1/items/{item_id}/attachments/{attachment_id}/download?token={token}",
        f"{base}/v1/items/{item_id}/attachments/{attachment_id}?token={token}",
    ]
    for url in urls:
        try:
            response = requests.get(url, timeout=30)
            if response.ok and response.content:
                destination.write_bytes(response.content)
                return True
        except requests.RequestException:
            continue
    return False


def main() -> None:
    _load_dotenv()
    client = _build_client()

    suffix = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
    created_location_id: str | None = None
    created_item_id: str | None = None

    try:
        location = client.locations.create_location(
            LocationCreate(name=f"Attachment Demo {suffix}", description="Created by item_attachements.py")
        )
        created_location_id = location.id
        item = client.items.create_item(
            ItemCreate(
                name=f"Attachment Item {suffix}", description="Item used for attachment demo", locationId=location.id
            )
        )
        created_item_id = item.id

        print(f"Created location: {location.id}")
        print(f"Created item: {item.id}")

        client.items.create_item_attachment(
            item.id,
            file=b"This is a text attachment generated by the example script.\n",
            type="manual",
            primary=False,
            name="readme.txt",
        )
        client.items.create_item_attachment(
            item.id,
            file=_tiny_png(),
            type="photo",
            primary=True,
            name="preview.png",
        )
        print("Uploaded two attachments")

        item_with_attachments = client.items.get_item(item.id)
        attachments = item_with_attachments.attachments or []
        print(f"Item {item.name} has {len(attachments)} attachment(s):")
        for att in attachments:
            print(f"- id={att.id}, title={att.title}, type={att.type}, primary={att.primary}")

        if not attachments or not attachments[0].id:
            print("No downloadable attachment found.")
            return

        # Download the first attachment.
        attachment_id = attachments[0].id
        attachment_data = client.items.get_item_attachment(item.id, attachment_id)
        if not attachment_data:
            print("Attachment endpoint returned no data.")
            return

        output_file = Path.cwd() / f"downloaded_{attachment_id[:8]}"
        output_file.write_bytes(attachment_data)
        print(f"Downloaded attachment to {output_file}")

        # Update the title of the first attachment.
        updated_item = client.items.update_item_attachment(
            item.id,
            attachment_id,
            ItemAttachmentUpdate(title="Updated readme", type="manual", primary=False),
        )
        updated_attachments = updated_item.attachments or []
        renamed = next((a for a in updated_attachments if a.id == attachment_id), None)
        if renamed:
            print(f"Updated attachment title to: {renamed.title}")

        # Delete the second attachment if present.
        if len(attachments) > 1 and attachments[1].id:
            client.items.delete_item_attachment(item.id, attachments[1].id)
            print(f"Deleted attachment: {attachments[1].id}")

    finally:
        if created_item_id:
            client.items.delete_item(created_item_id)
            print(f"Deleted item: {created_item_id}")

        if created_location_id:
            client.locations.delete_location(created_location_id)
            print(f"Deleted location: {created_location_id}")


if __name__ == "__main__":
    main()