Quick Start

This page shows the two most common setups — local filesystem and AWS S3 — so you can be productive in minutes.

Local Filesystem (Nginx)

Good for development, single-server deployments, or any setup where a static web server (Nginx, Apache, Caddy …) serves files directly from disk.

from granite_assets import (
    LocalNginxAssetRepositoryConfig,
    LocalNginxAssetRepository,
    AssetSaveRequest,
    AssetVisibility,
)

# 1. Configure the repository.
config = LocalNginxAssetRepositoryConfig(
    storage_path="/var/www/assets",      # absolute path on disk
    base_url="https://cdn.example.com/assets",  # how Nginx serves it
    public_prefix="public",             # default; public files go here
    private_prefix="private",           # private files (Nginx-protected)
)
repo = LocalNginxAssetRepository(config)

# 2. Save an asset — explicit key.
with open("photo.jpg", "rb") as f:
    result = repo.save(AssetSaveRequest(
        key="avatars/user-42.jpg",
        source=f,
        content_type="image/jpeg",
        visibility=AssetVisibility.PUBLIC,
        filename="photo.jpg",
    ))
print(result.key)            # avatars/user-42.jpg
print(result.content_length) # bytes written

# Auto-generated key — omit ``key``; granite-assets creates
# ``{uuid}/{uuid}.jpg`` automatically.
with open("photo.jpg", "rb") as f:
    result = repo.save(AssetSaveRequest(
        source=f,
        content_type="image/jpeg",
        visibility=AssetVisibility.PUBLIC,
        filename="photo.jpg",
    ))
print(result.key)  # e.g. "a1b2c3d4-.../<same-uuid>.jpg"

# 3. Build a public URL.
url = repo.build_public_url("avatars/user-42.jpg")
print(url.url)   # https://cdn.example.com/assets/public/avatars/user-42.jpg
print(url.is_permanent)  # True

# 4. Check existence.
assert repo.exists("avatars/user-42.jpg")

# 5. Get metadata (no download).
descriptor = repo.get_descriptor("avatars/user-42.jpg")
print(descriptor.content_length)
print(descriptor.last_modified)

# 6. Copy and move.
repo.copy("avatars/user-42.jpg", "avatars/backup/user-42.jpg")
repo.move("avatars/backup/user-42.jpg", "archive/user-42.jpg")

# 7. Delete.
repo.delete("avatars/user-42.jpg")

AWS S3

Replace the configuration object; the API is identical.

from granite_assets import (
    S3AssetRepositoryConfig,
    AssetSaveRequest,
    AssetVisibility,
    build_asset_repository,
)

config = S3AssetRepositoryConfig(
    bucket="my-app-assets",
    region="eu-west-1",
    key_prefix="production/",           # optional prefix inside the bucket
    public_base_url="https://cdn.example.com",  # CDN in front of the bucket
    presign_ttl_seconds=3600,           # default TTL for signed URLs
)
repo = build_asset_repository(config)   # returns S3AssetRepository

# Save a public asset — explicit key.
with open("banner.png", "rb") as f:
    result = repo.save(AssetSaveRequest(
        key="banners/homepage.png",
        source=f,
        content_type="image/png",
        visibility=AssetVisibility.PUBLIC,
    ))

# Save with auto-generated key (recommended for user uploads).
with open("invoice.pdf", "rb") as f:
    result = repo.save(AssetSaveRequest(
        source=f,
        content_type="application/pdf",
        visibility=AssetVisibility.PRIVATE,
        filename="invoice.pdf",
    ))
print(result.key)
# e.g. "3b105bc5-6056-4a52-b03b-7d953644c826/3b105bc5-....pdf"

# Permanent public URL (via CDN).
url = repo.build_public_url("banners/homepage.png")
print(url.url)   # https://cdn.example.com/banners/homepage.png

# Save a private asset.
with open("invoice.pdf", "rb") as f:
    repo.save(AssetSaveRequest(
        key="invoices/inv-001.pdf",
        source=f,
        content_type="application/pdf",
        visibility=AssetVisibility.PRIVATE,
    ))

# Time-limited download URL.
dl = repo.build_download_url("invoices/inv-001.pdf", ttl_seconds=300)
print(dl.url)        # https://... (presigned S3 URL)
print(dl.expires_at) # UTC datetime 5 minutes from now

Using the Factory

build_asset_repository inspects the config type and returns the right repository. This is useful in dependency-injection setups:

from granite_assets import build_asset_repository, LocalNginxAssetRepositoryConfig

def get_repo():
    # In production, read from environment / settings
    config = LocalNginxAssetRepositoryConfig(
        storage_path="/var/www/assets",
        base_url="http://localhost/assets",
    )
    return build_asset_repository(config)

Error Handling

from granite_assets import (
    AssetError,
    AssetNotFoundError,
    AssetAccessNotSupportedError,
)

try:
    repo.delete("missing-key.jpg")
except AssetNotFoundError as e:
    print(f"Key not found: {e.key}")

try:
    repo.build_upload_url("some/key.jpg", "image/jpeg")
except AssetAccessNotSupportedError as e:
    print(f"{e.backend}: {e.operation} not supported")

try:
    repo.save(bad_request)
except AssetError as e:
    # Base class for all granite-assets errors
    print(e)