Skip to content

Getting Started

This guide walks through the most common workflows: connecting to the API, uploading assets, organising with categories, searching, and handling errors.

Prerequisites

Install pycomad:

pip install pycomad

You need a running Comad DAM server and credentials for the ullav-user-management auth service.

Connecting and authenticating

from pycomad import ComadClient

client = ComadClient(
    api_url="http://localhost:8080",
    auth_url="http://localhost:8081",
)
info = client.login(email="you@example.com", password="secret")
print(info.username, info.roles)

If your DAM server and auth service share a hostname (e.g. behind a reverse proxy), you can omit auth_url:

client = ComadClient("https://dam.example.com")
client.login(email="you@example.com", password="secret")

Uploading an asset

assets.upload() creates the asset record and streams the file in a single call:

asset = client.assets.upload(
    "photo.jpg",
    creator="alice",
    is_private=False,
    description="Summer 2024 shoot",
    keywords="summer, outdoor, portrait",
)
print(f"Uploaded: {asset.name}  ({asset.size:,} bytes)  id={asset.id}")

For larger pipelines you can separate record creation from file upload:

record = client.assets.create("Report", "application/pdf")
client.assets.upload_file(record.id, "report.pdf")

Downloading and thumbnails

# Full file as bytes
raw = client.assets.download(asset.id)
with open("copy.jpg", "wb") as f:
    f.write(raw)

# PNG thumbnail (server-generated)
thumb = client.assets.thumbnail(asset.id)
with open("thumb.png", "wb") as f:
    f.write(thumb)

Organising with categories

Categories form a tree. Create a root category, then add child categories:

root = client.categories.create("Architecture", access_level="Global")
child = client.categories.create(
    "Interiors",
    access_level="Group",
    parent_id=root.id,
)

# Link an asset to a category
client.assets.add_category(asset.id, root.id)

# Retrieve the asset with its categories
detail = client.assets.get(asset.id)
for cat in detail.categories:
    print(cat.name)

Searching assets

Full-text and field-filtered search:

results = client.search.search(q="portrait", creator="alice")
for r in results:
    print(r.asset.name, r.asset.size)
    if r.metadata and r.metadata.exif:
        print("  Camera:", r.metadata.exif.get("camera_make"))

Geographic proximity search:

nearby = client.search.nearby(lat=53.3498, lon=-6.2603, radius_km=10)
for r in nearby:
    print(r.asset.name)

Metadata (EXIF / IPTC / XMP)

The server extracts metadata when you upload a file. You can also trigger a refresh:

meta = client.metadata.get(asset.id)
if meta.exif:
    print("Camera:", meta.exif.get("camera_make"), meta.exif.get("camera_model"))
    print("Taken:", meta.exif.get("datetime"))
if meta.iptc:
    print("Creator:", meta.iptc.get("creator"))
    print("Caption:", meta.iptc.get("caption"))

# Re-extract from the stored file
refreshed = client.metadata.refresh(asset.id)

Checking usage quotas

usage = client.assets.usage()
used_mb = usage.used_bytes // 1024 ** 2
limit_mb = (usage.storage_limit_bytes or 0) // 1024 ** 2
print(f"{used_mb} MB / {limit_mb or '∞'} MB used")
print(f"{usage.asset_count} assets, {usage.category_count} categories")

Custom field schemas

Teams can define typed custom fields that appear on every asset:

schema = client.custom_field_schemas.create(
    team_id="my-team",
    key="project_code",
    name="Project Code",
    field_type="String",
    required=False,
)

# Upload an asset with a custom field value
asset = client.assets.upload(
    "brief.pdf",
    custom_fields={"project_code": "PRJ-42"},
)

ZIP batch import

Upload a ZIP archive and the server mirrors the directory tree as a category hierarchy:

result = client.zip.upload("photos.zip", creator="alice", is_private=False)
print(f"Created {len(result.categories)} categories")
print(f"Uploaded {len(result.assets)} assets")
for err in result.errors:
    print("Warning:", err)

Error handling reference

Exception Raised when
ComadAuthError login() not called; 401 or 403 response
ComadNotFoundError 404 — resource does not exist
ComadValidationError 400 — invalid input
ComadServerError 5xx — server-side fault
ComadError Base class; catches all of the above