· Nacho Coll · Tutorials  · 10 min de lectura

Subir a IPFS con Python: Guía Completa con Ejemplos de Código

Sube archivos a IPFS desde Python usando la API REST de IPFS.NINJA. Guía completa con ejemplos de la librería requests.

Sube archivos a IPFS desde Python usando la API REST de IPFS.NINJA. Guía completa con ejemplos de la librería requests.

Los desarrolladores de Python que buscan subir archivos a IPFS no necesitan ejecutar un nodo IPFS local o lidiar con dependencias de librerías complejas. Aunque IPFS.NINJA no proporciona un SDK dedicado de Python, su API REST está perfectamente adaptada para aplicaciones Python usando la librería integrada requests.

Esta guía te muestra cómo subir archivos, imágenes y datos JSON a IPFS desde Python, gestionar tus archivos pineados, y aprovechar funciones avanzadas como tokens de subida firmados para subidas seguras del lado del cliente.

IPFS Ninja

¿Por Qué Elegir IPFS.NINJA para Desarrollo en Python?

Antes de adentrarnos en los ejemplos de código, entendamos por qué IPFS.NINJA es una excelente opción para los desarrolladores de Python:

  • Sin dependencias de SDK: Usa la librería integrada requests de Python
  • API RESTful: Endpoints limpios y predecibles que siguen los estándares HTTP
  • JSON-nativo: Sube datos JSON directamente sin conversión a archivo
  • Múltiples métodos de autenticación: Claves API, tokens firmados o JWT
  • Análisis integrados: Rastrea el rendimiento y uso de subidas
  • Gateways personalizados: Acceso IPFS de marca para tus aplicaciones

Si eres nuevo en los conceptos de pinning de IPFS, consulta nuestra guía completa sobre qué es el pinning de IPFS y cómo subir archivos a IPFS.

Configurando tu Entorno Python

Primero, asegúrate de tener Python 3.6+ e instala las librerías necesarias:

pip install requests

Para ejemplos de procesamiento de imágenes más adelante en esta guía:

pip install Pillow  # Para manejo de imágenes

Configuración de Autenticación

Todas las llamadas a la API de IPFS.NINJA requieren autenticación. El método más simple para aplicaciones Python es usar claves API con el header X-Api-Key.

import requests
import json
import base64

# Tu clave API de IPFS.NINJA (formato: bws_ + 32 caracteres hex)
API_KEY = "bws_1234567890abcdef1234567890abcdef"
BASE_URL = "https://api.ipfs.ninja"

# Headers por defecto para todas las peticiones
headers = {
    "X-Api-Key": API_KEY,
    "Content-Type": "application/json"
}

Obtén tu clave API desde el panel de IPFS.NINJA después de crear tu cuenta.

Subiendo Datos JSON a IPFS

Una de las características únicas de IPFS.NINJA es el soporte nativo de JSON. No necesitas convertir JSON a archivos — sube objetos directamente:

def upload_json_to_ipfs(data, description=None, metadata=None):
    """
    Subir datos JSON directamente a IPFS
    
    Args:
        data (dict): Los datos JSON a subir
        description (str, opcional): Descripción para el archivo
        metadata (dict, opcional): Metadatos adicionales
    
    Returns:
        dict: Respuesta conteniendo CID y URLs de acceso
    """
    payload = {
        "content": data,
        "description": description,
        "metadata": metadata
    }
    
    response = requests.post(
        f"{BASE_URL}/upload/new",
        headers=headers,
        json=payload
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Upload failed: {response.status_code} - {response.text}")

# Ejemplo: Subir un perfil de usuario
user_profile = {
    "name": "Alice Smith",
    "bio": "Python developer and IPFS enthusiast",
    "website": "https://alice.dev",
    "social": {
        "github": "alicesmith",
        "twitter": "@alice_dev"
    }
}

try:
    result = upload_json_to_ipfs(
        data=user_profile,
        description="User profile for Alice Smith",
        metadata={"type": "profile", "version": "1.0"}
    )
    
    print(f"✅ Upload successful!")
    print(f"CID: {result['cid']}")
    print(f"Size: {result['sizeMB']} MB")
    print(f"IPFS URL: {result['uris']['ipfs']}")
    print(f"Gateway URL: {result['uris']['url']}")
    
except Exception as e:
    print(f"❌ Upload failed: {e}")

Subiendo Archivos con Codificación Base64

Para archivos binarios como imágenes, documentos o cualquier tipo de archivo, usa codificación base64:

def upload_file_to_ipfs(file_path, description=None, metadata=None):
    """
    Subir un archivo a IPFS usando codificación base64
    
    Args:
        file_path (str): Ruta al archivo a subir
        description (str, opcional): Descripción para el archivo
        metadata (dict, opcional): Metadatos adicionales
    
    Returns:
        dict: Respuesta conteniendo CID y URLs de acceso
    """
    try:
        with open(file_path, "rb") as file:
            file_content = base64.b64encode(file.read()).decode('utf-8')
        
        payload = {
            "content": file_content,
            "description": description or f"File upload: {file_path}",
            "metadata": metadata
        }
        
        response = requests.post(
            f"{BASE_URL}/upload/new",
            headers=headers,
            json=payload
        )
        
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"Upload failed: {response.status_code} - {response.text}")
            
    except FileNotFoundError:
        raise Exception(f"File not found: {file_path}")

# Ejemplo: Subir una imagen
try:
    result = upload_file_to_ipfs(
        file_path="./logo.png",
        description="Company logo",
        metadata={
            "type": "image",
            "format": "png",
            "purpose": "branding"
        }
    )
    
    print(f"✅ Image uploaded successfully!")
    print(f"CID: {result['cid']}")
    print(f"View at: {result['uris']['url']}")
    
except Exception as e:
    print(f"❌ Upload failed: {e}")

Función de Subida por Lotes

Para subir múltiples archivos eficientemente:

import os
from pathlib import Path

def batch_upload_files(directory_path, file_extensions=None):
    """
    Subir múltiples archivos desde un directorio
    
    Args:
        directory_path (str): Directorio conteniendo archivos a subir
        file_extensions (list, opcional): Filtrar por extensiones de archivo (ej., ['.png', '.jpg'])
    
    Returns:
        list: Resultados de cada subida
    """
    results = []
    directory = Path(directory_path)
    
    if not directory.exists():
        raise Exception(f"Directory not found: {directory_path}")
    
    # Obtener todos los archivos o filtrar por extensiones
    if file_extensions:
        files = []
        for ext in file_extensions:
            files.extend(directory.glob(f"*{ext}"))
    else:
        files = [f for f in directory.iterdir() if f.is_file()]
    
    print(f"Found {len(files)} files to upload...")
    
    for file_path in files:
        try:
            print(f"Uploading {file_path.name}...")
            
            result = upload_file_to_ipfs(
                file_path=str(file_path),
                description=f"Batch upload: {file_path.name}",
                metadata={
                    "batch_upload": True,
                    "original_name": file_path.name,
                    "file_size": file_path.stat().st_size
                }
            )
            
            results.append({
                "filename": file_path.name,
                "success": True,
                "cid": result['cid'],
                "url": result['uris']['url']
            })
            
        except Exception as e:
            results.append({
                "filename": file_path.name,
                "success": False,
                "error": str(e)
            })
            print(f"Failed to upload {file_path.name}: {e}")
    
    return results

# Ejemplo de uso
try:
    results = batch_upload_files("./images", file_extensions=['.png', '.jpg', '.jpeg'])
    
    successful = [r for r in results if r['success']]
    failed = [r for r in results if not r['success']]
    
    print(f"\n✅ Successfully uploaded: {len(successful)} files")
    print(f"❌ Failed uploads: {len(failed)} files")
    
    for result in successful:
        print(f"  - {result['filename']}: {result['url']}")
        
except Exception as e:
    print(f"Batch upload error: {e}")

Gestionando tus Archivos Pineados

Recupera y gestiona tus archivos subidos:

def list_pinned_files(limit=50, offset=0):
    """
    Listar tus archivos pineados
    
    Args:
        limit (int): Número de archivos a recuperar (máx 100)
        offset (int): Número de archivos a omitir
    
    Returns:
        dict: Respuesta conteniendo lista de archivos y paginación
    """
    params = {
        "limit": limit,
        "offset": offset
    }
    
    response = requests.get(
        f"{BASE_URL}/files",
        headers=headers,
        params=params
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Failed to list files: {response.status_code} - {response.text}")

def get_file_details(cid):
    """
    Obtener detalles para un archivo específico
    
    Args:
        cid (str): El Identificador de Contenido del archivo
    
    Returns:
        dict: Detalles del archivo
    """
    response = requests.get(
        f"{BASE_URL}/files/{cid}",
        headers=headers
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Failed to get file details: {response.status_code} - {response.text}")

# Ejemplo: Listar subidas recientes
try:
    files = list_pinned_files(limit=10)
    
    print(f"Total files: {files.get('total', 0)}")
    print("\nRecent uploads:")
    
    for file_info in files.get('files', []):
        print(f"  - {file_info['cid'][:12]}... | {file_info['sizeMB']} MB | {file_info['description']}")
        
except Exception as e:
    print(f"Failed to list files: {e}")

Pinear Contenido IPFS Existente

Pinea contenido que ya existe en IPFS:

def pin_existing_cid(cid, description=None):
    """
    Pinear contenido IPFS existente por CID
    
    Args:
        cid (str): El Identificador de Contenido a pinear
        description (str, opcional): Descripción para el contenido pineado
    
    Returns:
        dict: Respuesta con estado del pin
    """
    payload = {
        "cid": cid,
        "description": description or f"Pinned existing content: {cid}"
    }
    
    response = requests.post(
        f"{BASE_URL}/pin",
        headers=headers,
        json=payload
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Pin failed: {response.status_code} - {response.text}")

# Ejemplo: Pinear metadata NFT popular
try:
    result = pin_existing_cid(
        cid="QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
        description="Popular NFT metadata backup"
    )
    
    print(f"✅ Successfully pinned existing content!")
    print(f"CID: {result['cid']}")
    
except Exception as e:
    print(f"❌ Pin failed: {e}")

Usando Tokens de Subida Firmados

Para subidas seguras del lado del cliente o acceso temporal, usa tokens firmados:

def create_upload_token(expires_in_hours=24, max_uses=None):
    """
    Crear un token de subida firmado para subidas temporales
    
    Args:
        expires_in_hours (int): Tiempo de expiración del token en horas
        max_uses (int, opcional): Número máximo de usos (ilimitado si es None)
    
    Returns:
        dict: Detalles del token incluyendo la cadena del token firmado
    """
    from datetime import datetime, timedelta
    
    expiry_time = datetime.utcnow() + timedelta(hours=expires_in_hours)
    
    payload = {
        "expiry": expiry_time.isoformat() + "Z",
        "maxUses": max_uses
    }
    
    response = requests.post(
        f"{BASE_URL}/upload-tokens",
        headers=headers,
        json=payload
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Token creation failed: {response.status_code} - {response.text}")

def upload_with_token(data, token, description=None):
    """
    Subir usando un token firmado en lugar de clave API
    
    Args:
        data: Contenido a subir (dict para JSON, cadena base64 para archivos)
        token (str): Token de subida firmado
        description (str, opcional): Descripción para la subida
    
    Returns:
        dict: Respuesta de subida
    """
    token_headers = {
        "Authorization": f"Signed {token}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "content": data,
        "description": description
    }
    
    response = requests.post(
        f"{BASE_URL}/upload/new",
        headers=token_headers,
        json=payload
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Token upload failed: {response.status_code} - {response.text}")

# Ejemplo: Crear token para subidas del lado del cliente
try:
    token_info = create_upload_token(expires_in_hours=1, max_uses=10)
    upload_token = token_info['token']
    
    print(f"Created upload token: {upload_token[:20]}...")
    print(f"Expires: {token_info['expiry']}")
    print(f"Max uses: {token_info.get('maxUses', 'unlimited')}")
    
    # Usar el token para una subida
    test_data = {"message": "Hello from signed token!"}
    result = upload_with_token(test_data, upload_token, "Token test upload")
    
    print(f"✅ Token upload successful: {result['cid']}")
    
except Exception as e:
    print(f"❌ Token operation failed: {e}")

Ejemplo Completo: Cargador de Metadata NFT

Aquí hay un ejemplo práctico que combina múltiples conceptos para la gestión de metadata NFT:

class NFTMetadataUploader:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://api.ipfs.ninja"
        self.headers = {
            "X-Api-Key": api_key,
            "Content-Type": "application/json"
        }
    
    def upload_nft_image(self, image_path, name, description=""):
        """Subir imagen NFT y devolver URL IPFS"""
        try:
            with open(image_path, "rb") as file:
                image_content = base64.b64encode(file.read()).decode('utf-8')
            
            payload = {
                "content": image_content,
                "description": f"NFT Image: {name}",
                "metadata": {
                    "type": "nft_image",
                    "name": name,
                    "description": description
                }
            }
            
            response = requests.post(
                f"{self.base_url}/upload/new",
                headers=self.headers,
                json=payload
            )
            
            if response.status_code == 200:
                result = response.json()
                return result['uris']['ipfs']  # Devolver URI IPFS
            else:
                raise Exception(f"Image upload failed: {response.text}")
                
        except Exception as e:
            raise Exception(f"Failed to upload image {image_path}: {e}")
    
    def upload_nft_metadata(self, name, description, image_ipfs_url, attributes=None, external_url=None):
        """Subir metadata NFT completa"""
        metadata = {
            "name": name,
            "description": description,
            "image": image_ipfs_url,
        }
        
        if attributes:
            metadata["attributes"] = attributes
        if external_url:
            metadata["external_url"] = external_url
        
        payload = {
            "content": metadata,
            "description": f"NFT Metadata: {name}",
            "metadata": {
                "type": "nft_metadata",
                "standard": "ERC-721"
            }
        }
        
        response = requests.post(
            f"{self.base_url}/upload/new",
            headers=self.headers,
            json=payload
        )
        
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"Metadata upload failed: {response.text}")
    
    def create_complete_nft(self, image_path, name, description, attributes=None, external_url=None):
        """Subir imagen y metadata para un NFT completo"""
        print(f"Creating NFT: {name}")
        
        # Paso 1: Subir imagen
        print("  1. Uploading image...")
        image_ipfs_url = self.upload_nft_image(image_path, name, description)
        print(f"     Image IPFS URL: {image_ipfs_url}")
        
        # Paso 2: Subir metadata
        print("  2. Uploading metadata...")
        metadata_result = self.upload_nft_metadata(
            name=name,
            description=description,
            image_ipfs_url=image_ipfs_url,
            attributes=attributes,
            external_url=external_url
        )
        
        print(f"     Metadata CID: {metadata_result['cid']}")
        print(f"     Metadata URL: {metadata_result['uris']['url']}")
        
        return {
            "image_ipfs": image_ipfs_url,
            "metadata_cid": metadata_result['cid'],
            "metadata_url": metadata_result['uris']['url'],
            "metadata_ipfs": metadata_result['uris']['ipfs']
        }

# Ejemplo de uso
if __name__ == "__main__":
    uploader = NFTMetadataUploader("bws_1234567890abcdef1234567890abcdef")
    
    try:
        nft_result = uploader.create_complete_nft(
            image_path="./nft_artwork.png",
            name="Cosmic Cat #001",
            description="A rare cosmic cat exploring the digital universe",
            attributes=[
                {"trait_type": "Background", "value": "Space"},
                {"trait_type": "Species", "value": "Cosmic Cat"},
                {"trait_type": "Rarity", "value": "Legendary"},
                {"trait_type": "Power Level", "value": 95}
            ],
            external_url="https://myproject.com/nft/1"
        )
        
        print("\n🎉 NFT created successfully!")
        print(f"Use this metadata URI in your smart contract: {nft_result['metadata_ipfs']}")
        
    except Exception as e:
        print(f"❌ NFT creation failed: {e}")

Manejo de Errores y Mejores Prácticas

Implementa un manejo de errores robusto para aplicaciones de producción:

import time
from functools import wraps

def retry_on_failure(max_retries=3, delay=1):
    """Decorador para reintentar subidas fallidas"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries - 1:
                        raise e
                    print(f"Attempt {attempt + 1} failed: {e}")
                    print(f"Retrying in {delay} seconds...")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry_on_failure(max_retries=3, delay=2)
def robust_upload(file_path, description=None):
    """Subir con reintento automático en caso de fallo"""
    return upload_file_to_ipfs(file_path, description)

def validate_api_response(response, operation="operation"):
    """Validar respuesta API y proporcionar info de error detallada"""
    if response.status_code == 200:
        return response.json()
    elif response.status_code == 401:
        raise Exception(f"Authentication failed for {operation}. Check your API key.")
    elif response.status_code == 413:
        raise Exception(f"File too large for {operation}. Check your plan limits.")
    elif response.status_code == 429:
        raise Exception(f"Rate limit exceeded for {operation}. Please wait and retry.")
    else:
        raise Exception(f"{operation} failed: {response.status_code} - {response.text}")

Próximos Pasos

¡Ahora tienes un kit de herramientas completo para subir archivos a IPFS desde Python! Esto cubre:

  • Subidas JSON y de archivos con la API de IPFS.NINJA
  • Procesamiento por lotes para múltiples archivos
  • Gestión de archivos y pinning de contenido existente
  • Subidas seguras con tokens firmados
  • Manejo de errores listo para producción

Para funciones más avanzadas, explora nuestras otras guías:

La API de IPFS.NINJA proporciona todo lo que necesitas para aplicaciones IPFS basadas en Python sin la complejidad de ejecutar tu propia infraestructura.

¿Listo para empezar a pinear? Crea una cuenta gratuita — 50 archivos, 1 GB de almacenamiento, 2 GB de ancho de banda/mes. Sin tarjeta de crédito.

Volver al Blog

Artículos Relacionados

Ver Todos los Artículos »