Skip to main content

High-level Python client for the Blossom protocol (blob storage via Nostr auth)

Project description

python-blossom

python-blossom logo

High-level Python client for the Blossom protocol (blob storage via Nostr auth).

Implements core BUDs:

  • BUD-01: Blob retrieval (GET/HEAD)
  • BUD-02: Upload, List, Delete
  • BUD-03: User Server List event generation (kind 10063)
  • BUD-04: Mirror blob
  • BUD-05: Media optimization (optional endpoints)
  • BUD-06: Upload requirements (HEAD /upload)

Installation

Install from PyPI:

pip install python-blossom

Or install from GitHub:

pip install git+https://github.com/Jxck-S/python-blossom.git

Quick Start

from python_blossom import BlossomClient

nsec = 'nsec....'  # Your Nostr private key (nsec)
servers = [
    'https://blossom.band',
    'https://nostr.download',
    'https://blossom.primal.net'
]

client = BlossomClient(nsec=nsec, default_servers=servers)
# Optional: set a custom request timeout (default is 30 seconds)
# client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)

# Upload blob data
result = client.upload_blob(servers[0], data=b'blob data', mime_type='image/png')
sha256 = result['sha256']
print(f"Uploaded: {result['url']}")

# Get blob
blob = client.get_blob(servers[0], sha256, mime_type='image/png')
downloaded_data = blob.get_bytes()
print(f"Downloaded: {len(downloaded_data)} bytes")

# HEAD request to check blob metadata
meta = client.head_upload_requirements(servers[0], data=b'blob data', mime_type='image/png')
print(f"Server supports: {meta}")

# List user's blobs
blobs = client.list_blobs(servers[0], pubkey=client.pubkey_hex, use_auth=True)
print(f"Found {len(blobs)} blobs")

# Delete blob
client.delete_blob(servers[0], sha256, description='Cleanup')

# Mirror blob to another server
mirror_result = client.mirror_blob(servers[1], servers[0], sha256)
print(f"Mirrored to: {mirror_result['url']}")

# Generate User Server List event (kind 10063, BUD-03)
event = client.generate_server_list_event(servers=servers)
print(f"Event ID: {event['id']}")

# Publish server list event to relays
event_id = client.publish_server_list_event(relays=['wss://relay.damus.io'])
print(f"Published: {event_id}")

Publishing Media to Nostr with Redundancy

Here's how to upload a photo to multiple Blossom servers and publish it to Nostr with automatic failover:

from python_blossom import BlossomClient
from pynostr.event import Event
from pynostr.key import PrivateKey
import json

nsec = 'nsec....'  # Your Nostr private key
servers = [
    'https://blossom.band',
    'https://nostr.download',
    'https://blossom.primal.net'
]

client = BlossomClient(nsec=nsec, default_servers=servers)

# Step 1: Publish your server list to Nostr (do this once or when you change servers)
# This advertises all your Blossom servers to the Nostr network
# Clients use this to find backup servers if the primary one is down
relays = ['wss://relay.damus.io', 'wss://relay.snort.social']
client.publish_server_list_event(relays=relays, servers=servers)
print(f"✓ Published server list to relays")

# Step 2: Upload photo to ALL servers for redundancy
with open('photo.jpg', 'rb') as f:
    photo_data = f.read()

upload_results = client.upload_to_all(
    data=photo_data,
    mime_type='image/jpeg',
    description='My vacation photo'
)

# Get the primary URL (first successful upload)
primary_url = None
for server, result in upload_results.items():
    if 'url' in result and not 'error' in result:
        primary_url = result['url']
        sha256 = result['sha256']
        print(f"✓ Uploaded to {server}: {primary_url}")
        break

# Step 3: Create Nostr note with media tag pointing to primary URL
# If the primary server goes down, clients will use your server list event 
# (kind 10063) to find the photo on your other servers
private_key = PrivateKey.from_nsec(nsec)
event = Event(
    content="Check out this photo! 📸",
    kind=1  # Text note
)

# Add image tag with primary URL
# Format: ["image", url, "m" MIME type, "alt" description, "x" sha256]
event.add_tag("image", primary_url, "m", "image/jpeg", "alt", "My vacation photo", "x", sha256)

event.sign(private_key.hex())

# Publish to relays using pynostr
from pynostr.relay_manager import RelayManager

relay_manager = RelayManager(timeout=5)
for relay in relays:
    relay_manager.add_relay(relay)

relay_manager.publish_event(event)
relay_manager.run_sync()
relay_manager.close_all_relay_connections()

Timeout Configuration

All HTTP requests use a default timeout of 30 seconds (configurable per-client). Without a timeout, a slow or unresponsive Blossom server can cause the calling thread to hang indefinitely.

from python_blossom import BlossomClient, DEFAULT_TIMEOUT

# Use the default (30s)
client = BlossomClient(nsec=nsec, default_servers=servers)

# Set a shorter timeout for all requests made by this client
client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)

# Disable timeout entirely (not recommended)
client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)

# Inspect the default
print(DEFAULT_TIMEOUT)  # 30.0

The timeout value is passed directly to requests and applies to both the connect and read phases of every HTTP call. To set separate connect vs. read timeouts, pass a tuple:

client = BlossomClient(nsec=nsec, timeout=(5, 30))  # 5s connect, 30s read

On timeout, requests.exceptions.Timeout is raised.

API Methods

Upload & Download

upload_blob(server, data=None, file_path=None, mime_type=None, description=None, use_auth=False)

  • Upload blob data (raw bytes or file)
  • Auto-detects MIME type if not specified
  • Returns blob descriptor with sha256, url, size, type

get_blob(server, sha256, mime_type=None, use_auth=False)

  • Download blob by SHA256 hash
  • Returns Blob object with content, sha256, mime_type properties
  • Call blob.get_bytes() or blob.save(file_path) to access data

head_upload_requirements(server, data=None, file_path=None, mime_type=None, use_auth=False)

  • Check server upload requirements before uploading
  • Returns headers: content_type, content_length, accept_ranges

Media (BUD-05)

media_upload(server, data=None, file_path=None, mime_type=None, description=None, use_auth=False)

  • Upload blob with media optimization
  • Returns blob descriptor

media_head(server, data=None, file_path=None, mime_type=None, use_auth=False)

  • Check media upload requirements
  • Returns headers

Blob Management

upload_blob(server, data=None, file_path=None, mime_type=None, description=None, use_auth=True)

  • Upload blob data (raw bytes or file)
  • Auto-detects MIME type if not specified
  • Returns blob descriptor with sha256, url, size, type

upload_to_all(data=None, file_path=None, mime_type=None, description=None, use_auth=True)

  • Upload blob to all default servers in parallel
  • Uses servers configured during client initialization
  • Returns dict mapping server URLs to results/errors

list_blobs(server, pubkey=None, cursor=None, limit=None, use_auth=False)

  • List blobs for a public key (accepts npub or hex format)
  • Returns list of blob descriptors
  • Requires use_auth=True for private server listings

delete_blob(server, sha256, description=None)

  • Delete blob from server
  • Requires authorization
  • Returns confirmation

mirror_blob(server, source_url, sha256, description=None)

  • Mirror blob from one server to another
  • Returns blob descriptor on destination server

Head Blob

head_blob(server, sha256, use_auth=False)

  • Get blob metadata without downloading content
  • Returns headers: content_type, content_length, accept_ranges

Server Management (BUD-03)

Server list events (Nostr kind 10063) are like "advertising" - they publicly announce which Blossom servers a user uploads media to. When you publish a server list event to the Nostr network, anyone can look up your public key and discover which servers you use. NOSTR clients use this to know where to look for published media in a NOSTR note.

generate_server_list_event(servers=None)

  • Generate a Nostr server list event (kind 10063, BUD-03)
  • Contains tags with your Blossom server URLs
  • Automatically signed with your private key
  • Returns Nostr event dict ready for publishing

publish_server_list_event(relays, servers=None)

  • Publish your server list event to Nostr relays
  • Advertises your Blossom servers publicly by your public key
  • relays: List of relay URLs (wss://) where the event will be published
  • servers: Optional list of server URLs (defaults to configured servers on the client)
  • Returns event ID of the published event

fetch_server_list(relays, pubkey=None, timeout=2.0)

  • Look up which Blossom servers a user has advertised
  • Query Nostr relays for a user's server list event
  • relays: List of relay URLs to query
  • pubkey: Optional public key in any format (npub or hex) to look up (defaults to your own)
  • Returns list of server URLs that user has advertised

Authorization

Methods with use_auth=True parameter automatically generate NIP-98 authorization events:

  • kind 24242
  • Tags: t=<verb>, expiration, x=<hash> (when applicable)
  • Base64 encoded in Authorization: Nostr <base64> header
  • Auto-signed with your private key

Error Handling

Raises BlossomError or specific subclasses on errors:

  • InvalidAuthorizationEvent: 401 auth failure
  • BlobNotFound: 404 blob not found
  • TooManyRequests: 429 rate limit
  • BlossomError: Other HTTP errors
from python_blossom.errors import BlobNotFound, TooManyRequests

try:
    blob = client.get_blob(server, sha256)
except BlobNotFound:
    print("Blob not found")
except TooManyRequests:
    print("Rate limited - try again later")

TODO / Unsupported Endpoints

The following Blossom endpoints are not yet implemented:

  • BUD-09: PUT /report - Report abuse/misinformation (planned for future release)

License

Unlicense / Public Domain (matches upstream Blossom spec repository).

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

python_blossom-1.0.3.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

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

python_blossom-1.0.3-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

File details

Details for the file python_blossom-1.0.3.tar.gz.

File metadata

  • Download URL: python_blossom-1.0.3.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for python_blossom-1.0.3.tar.gz
Algorithm Hash digest
SHA256 443f436b59686af7be2c802644192214c4743610fb7ef7c048d85fd6b7b547d8
MD5 390b482ba9214ec0729111973a680ad3
BLAKE2b-256 92b869ca2106a70db924565e94aa38d5e3d3f2aebd560ea06c29a57863f440b7

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_blossom-1.0.3.tar.gz:

Publisher: release.yml on Jxck-S/python-blossom

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

File details

Details for the file python_blossom-1.0.3-py3-none-any.whl.

File metadata

  • Download URL: python_blossom-1.0.3-py3-none-any.whl
  • Upload date:
  • Size: 14.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for python_blossom-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ddc5beff3a9ad3157450b7b193b6426d1e6083a6dcb181c286a7c4b3320ab59f
MD5 a4530e8933a600f2295ee44fdf5c42e3
BLAKE2b-256 a89685465469b9017876a1dadd2bfa9bbc577aa39d789c58dc8afb7ceedc5d10

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_blossom-1.0.3-py3-none-any.whl:

Publisher: release.yml on Jxck-S/python-blossom

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