# Guía del SDK de Python

{% hint style="success" %}
**¿Nuevo en el SDK?** Empieza con el [inicio rápido de 5 minutos](/guides/guides_v2-es/primeros-pasos/python-quickstart.md) primero.
{% endhint %}

Para un tutorial de inicio rápido con ejemplos reales, consulta [Clore.ai Python SDK — Automatiza tus flujos de trabajo con GPU en 5 minutos](https://blog.clore.ai/cloreai-python-sdk-automate-your-gpu-workflows-in-5-minutes/)

## Instalación

```bash
pip install clore-ai
```

El SDK proporciona dos clientes:

* **`CloreAI`** — sincrónico (más simple, bueno para scripts)
* **`AsyncCloreAI`** — asincrónico (más rápido para operaciones concurrentes)

Ambos comparten los mismos métodos y devuelven los mismos modelos Pydantic.

***

## Sincrónico vs Asincrónico — Cuándo usar cada uno

| Caso de uso                       | Cliente        | Por qué                                    |
| --------------------------------- | -------------- | ------------------------------------------ |
| Scripts simples, tareas puntuales | `CloreAI`      | Código más simple, sin `async/await`       |
| Bucles de monitorización          | `CloreAI`      | Comprobaciones secuenciales funcionan bien |
| Consultas masivas al marketplace  | `AsyncCloreAI` | Solicitudes concurrentes = más rápido      |
| Creación por lotes de órdenes     | `AsyncCloreAI` | Crear múltiples órdenes en paralelo        |
| Aplicaciones web                  | `AsyncCloreAI` | E/S no bloqueante                          |

### Ejemplo sincrónico

```python
from clore_ai import CloreAI

client = CloreAI()  # Usa la variable de entorno CLORE_API_KEY

servers = client.marketplace(gpu="RTX 4090")
print(f"Se encontraron {len(servers)} servidores")

client.close()  # O utiliza un gestor de contexto
```

### Ejemplo asincrónico

```python
import asyncio
from clore_ai import AsyncCloreAI

async def main():
    async with AsyncCloreAI() as client:
        servers = await client.marketplace(gpu="RTX 4090")
        print(f"Se encontraron {len(servers)} servidores")

asyncio.run(main())
```

### Gestores de contexto (recomendado)

Ambos clientes soportan gestores de contexto para limpieza automática:

```python
# Sincrónico
with CloreAI() as client:
    wallets = client.wallets()

# Asincrónico
async with AsyncCloreAI() as client:
    wallets = await client.wallets()
```

***

## Configuración del cliente

```python
client = CloreAI(
    api_key="tu_clave",      # O establece la variable de entorno CLORE_API_KEY
    base_url="https://api.clore.ai/v1",  # Endpoint de API personalizado
    timeout=30.0,            # Tiempo de espera de la solicitud en segundos
    max_retries=3            # Reintentos en límites de tasa / errores de red
)
```

El SDK incluye un limitador de tasa integrado:

* **Solicitudes generales:** 1 solicitud/segundo
* **`create_order`:** enfriamiento de 5 segundos entre llamadas
* **Errores por límite de tasa (código 5):** Reintento exponencial automático

***

## Filtrado del marketplace

El `marketplace()` método obtiene todos los servidores disponibles y filtra del lado del cliente:

```python
from clore_ai import CloreAI

client = CloreAI()

# Todos los servidores disponibles
all_servers = client.marketplace()

# Filtrar por modelo de GPU (coincidencia de subcadena sin distinguir mayúsculas)
rtx_4090s = client.marketplace(gpu="RTX 4090")

# Filtrar por múltiples criterios
budget_gpus = client.marketplace(
    gpu="RTX 4090",
    max_price_usd=1.0,       # Máx $1.00/hora
    min_gpu_count=2,          # Al menos 2 GPUs
    min_ram_gb=64.0,          # Al menos 64 GB de RAM del sistema
    available_only=True       # Solo servidores disponibles (por defecto)
)
```

### Filtrado avanzado (lado del cliente)

Para filtros no incorporados en el método, filtra los `Server` objetos devueltos tú mismo:

```python
servers = client.marketplace(gpu="RTX 4090")

# Servidores en la UE con alta fiabilidad
eu_servers = [
    s for s in servers
    if s.location and s.location.upper() in ("DE", "FR", "NL", "FI")
    and s.reliability and s.reliability >= 0.95
]

# Ordenar por precio
cheapest = sorted(servers, key=lambda s: s.price_usd or float("inf"))
print(f"El más barato: Servidor {cheapest[0].id} — ${cheapest[0].price_usd:.4f}/h")
```

### Campos del modelo Server

Cada `MarketplaceServer` objeto tiene estos atributos y propiedades de conveniencia:

| Campo            | Tipo                  | Descripción                                                             |
| ---------------- | --------------------- | ----------------------------------------------------------------------- |
| `id`             | `int`                 | ID del servidor (usa esto en `create_order`)                            |
| `gpu_model`      | `str \| None`         | Descripción de la GPU según las especificaciones (propiedad)            |
| `gpu_count`      | `int`                 | Número de GPUs de `gpu_array` (propiedad)                               |
| `ram_gb`         | `float \| None`       | RAM del sistema en GB (propiedad, desde `specs.ram`)                    |
| `price_usd`      | `float \| None`       | Precio bajo demanda en USD (propiedad, desde `price.usd.on_demand_usd`) |
| `spot_price_usd` | `float \| None`       | Precio spot en USD (propiedad)                                          |
| `available`      | `bool`                | Si el servidor no está alquilado (propiedad)                            |
| `location`       | `str \| None`         | Código de país desde las especificaciones de red (propiedad)            |
| `specs`          | `ServerSpecs \| None` | Especificaciones de hardware (CPU, RAM, disco, GPU, red)                |
| `price`          | `ServerPrice \| None` | Estructura de precios completa                                          |
| `rented`         | `bool \| None`        | Si el servidor está actualmente alquilado                               |

***

## Gestión de órdenes

### Creación de órdenes

```python
order = client.create_order(
    server_id=142,
    image="cloreai/ubuntu22.04-cuda12",
    type="on-demand",               # "on-demand" o "spot"
    currency="bitcoin",             # Moneda de pago
    ssh_password="MySecurePass",    # Acceso SSH
    ports={"22": "tcp", "8888": "http"},  # Mapas de puertos
    env={"HF_TOKEN": "hf_xxx"},    # Variables de entorno
    command="bash /start.sh",       # Comando de inicio personalizado
    jupyter_token="my_token"        # Token de Jupyter notebook
)

print(f"ID de la orden: {order.id}")
print(f"IP: {order.pub_cluster}")
print(f"Puertos: {order.tcp_ports}")
```

### Parámetros `create_order` Completos

| Parámetro            | Tipo    | Requerido | Descripción                          |
| -------------------- | ------- | --------- | ------------------------------------ |
| `server_id`          | `int`   | ✅         | Servidor a alquilar                  |
| `image`              | `str`   | ✅         | Imagen Docker                        |
| `type`               | `str`   | ✅         | `"on-demand"` o `"spot"`             |
| `currency`           | `str`   | ✅         | Moneda de pago (p. ej., `"bitcoin"`) |
| `ssh_password`       | `str`   | —         | Contraseña SSH                       |
| `ssh_key`            | `str`   | —         | Clave pública SSH                    |
| `ports`              | `dict`  | —         | Mapeos de puertos (`{"22": "tcp"}`)  |
| `env`                | `dict`  | —         | Variables de entorno                 |
| `jupyter_token`      | `str`   | —         | Token de Jupyter notebook            |
| `command`            | `str`   | —         | Comando de inicio                    |
| `spot_price`         | `float` | —         | Precio de oferta spot                |
| `required_price`     | `float` | —         | Precio requerido                     |
| `autossh_entrypoint` | `str`   | —         | Punto de entrada Auto SSH            |

### Listado de órdenes

```python
# Solo órdenes activas
active = client.my_orders()
for o in active:
    print(f"Orden {o.id}: type={o.type}, IP={o.pub_cluster}, status={o.status}")

# Incluir órdenes completadas
all_orders = client.my_orders(include_completed=True)
```

### Campos del modelo Order

| Campo         | Tipo            | Descripción                                 |
| ------------- | --------------- | ------------------------------------------- |
| `id`          | `int`           | ID de la orden                              |
| `server_id`   | `int \| None`   | ID del servidor alquilado                   |
| `type`        | `str`           | `"on-demand"` o `"spot"`                    |
| `status`      | `str \| None`   | Estado de la orden                          |
| `image`       | `str \| None`   | Imagen Docker                               |
| `currency`    | `str \| None`   | Moneda de pago                              |
| `price`       | `float \| None` | Precio                                      |
| `pub_cluster` | `str \| None`   | IP pública / nombre de host                 |
| `tcp_ports`   | `dict \| None`  | Mapeos de puertos (p. ej., `{"22": 50022}`) |
| `created_at`  | `str \| None`   | Marca temporal de creación                  |

### Monitorización de órdenes

```python
import time

def wait_for_ready(client, order_id, timeout=120):
    """Esperar a que una orden obtenga una IP pública."""
    for _ in range(timeout // 10):
        orders = client.my_orders()
        order = next((o for o in orders if o.id == order_id), None)
        if order and order.pub_cluster:
            return order
        time.sleep(10)
    raise TimeoutError(f"Orden {order_id} no lista después de {timeout}s")

# Uso
order = client.create_order(server_id=142, image="cloreai/ubuntu22.04-cuda12", type="on-demand", currency="bitcoin")
ready = wait_for_ready(client, order.id)
print(f"SSH: ssh root@{ready.pub_cluster} -p {ready.tcp_ports.get('22', 22)}")
```

### Cancelación de órdenes

```python
# Cancelar con razón opcional
client.cancel_order(order_id=38, issue="Trabajo completado")

# Cancelar todas las órdenes activas
orders = client.my_orders()
for order in orders:
    client.cancel_order(order.id, issue="Limpieza")
    print(f"Orden cancelada {order.id}")
```

***

## Gestión de servidores (para anfitriones)

Si alojas GPUs en Clore, el SDK te permite gestionar tus servidores:

### Listar tus servidores

```python
my_servers = client.my_servers()
for s in my_servers:
    print(f"Servidor {s.id}: {s.gpu_model} — {s.status}")
```

### Obtener configuración del servidor

```python
config = client.server_config("MyGPU-Rig")
print(f"Nombre: {config.name}")
print(f"Visibilidad: {config.visibility}")
print(f"En línea: {config.online}")
print(f"Alquiler mínimo: {config.mrl}h")
print(f"Precio on-demand: {config.on_demand_price}")
print(f"Precio spot: {config.spot_price}")
```

### Actualizar ajustes del servidor

```python
client.set_server_settings(
    name="MyGPU-Rig",
    availability=True,       # Hacer el servidor disponible
    mrl=24,                  # Alquiler mínimo 24h
    on_demand=0.0001,        # Precio on-demand en BTC
    spot=0.00000113          # Precio spot en BTC
)
print("Ajustes actualizados")
```

***

## Mercado spot

Las órdenes spot son 30–50% más baratas pero pueden ser interrumpidas si alguien te supera la puja.

### Ver ofertas spot

```python
offers = client.spot_marketplace(server_id=6)
for offer in offers:
    print(f"Orden {offer.get('order_id')}: price={offer.get('price')}")
```

### Crear una orden spot

```python
order = client.create_order(
    server_id=142,
    image="cloreai/ubuntu22.04-cuda12",
    type="spot",
    currency="bitcoin",
    spot_price=0.0001,       # Tu precio de puja
    ssh_password="MyPass"
)
print(f"Orden spot {order.id} creada")
```

### Ajustar precio spot

```python
# Aumenta tu puja para evitar ser superado
client.set_spot_price(order_id=39, price=0.000003)
```

### Estrategia de puja spot

```python
from clore_ai import CloreAI

client = CloreAI()

def smart_spot_bid(server_id, premium_pct=5):
    """Pujar ligeramente por encima del precio mínimo spot actual."""
    offers = client.spot_marketplace(server_id=server_id)
    if not offers:
        print("No hay ofertas spot — usa el precio on-demand como referencia")
        return None

    min_price = min(o["price"] for o in offers)
    bid = min_price * (1 + premium_pct / 100)
    print(f"Mínimo del mercado: {min_price}, pujando: {bid:.8f} (+{premium_pct}%)")
    return bid

# Uso
bid = smart_spot_bid(server_id=142, premium_pct=10)
if bid:
    order = client.create_order(
        server_id=142,
        image="cloreai/ubuntu22.04-cuda12",
        type="spot",
        currency="bitcoin",
        spot_price=bid
    )
```

***

## Operaciones con billeteras

### Comprobar saldos

```python
wallets = client.wallets()
for w in wallets:
    print(f"{w.name}: {w.balance:.8f}")
    if w.deposit:
        print(f"  Dirección de depósito: {w.deposit}")
```

### Alerta de saldo bajo

```python
from clore_ai import CloreAI

def check_balance(min_btc=0.001):
    """Alertar si el saldo en BTC está por debajo del umbral."""
    client = CloreAI()
    wallets = client.wallets()

    for w in wallets:
        if w.name.lower() == "bitcoin" and w.balance < min_btc:
            print(f"⚠️  Saldo BTC bajo: {w.balance:.8f} (mínimo: {min_btc})")
            return False

    print("✅ Saldos OK")
    return True

check_balance(min_btc=0.001)
```

***

## Buenas prácticas para el manejo de errores

### Jerarquía de excepciones

```
CloreAPIError (base)
├── DBError           (código 1) — error de base de datos
├── InvalidInputError (código 2) — entrada incorrecta
├── AuthError         (código 3) — clave API inválida
├── InvalidEndpointError (código 4) — endpoint incorrecto
├── RateLimitError    (código 5) — límite de tasa (reintentado automáticamente)
└── FieldError        (código 6) — error específico de campo
```

### Manejo básico de errores

```python
from clore_ai import CloreAI
from clore_ai.exceptions import (
    CloreAPIError,
    AuthError,
    RateLimitError,
    InvalidInputError
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=999999,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin"
    )
except AuthError:
    print("Clave API inválida — verifica CLORE_API_KEY")
except InvalidInputError as e:
    print(f"Entrada incorrecta: {e}")
except RateLimitError:
    print("Límite de tasa — el SDK reintenta automáticamente, pero se excedió el máximo de reintentos")
except CloreAPIError as e:
    print(f"Error de API (código {e.code}): {e}")
```

### Patrón de reintento con backoff

El SDK tiene reintentos integrados para límites de tasa y errores de red (`max_retries=3`). Para reintentos a nivel de aplicación:

```python
import time
from clore_ai import CloreAI
from clore_ai.exceptions import CloreAPIError, RateLimitError

def retry_operation(func, max_attempts=3, base_delay=2.0):
    """Reintentar una operación de la API de Clore con backoff exponencial."""
    for attempt in range(max_attempts):
        try:
            return func()
        except RateLimitError:
            if attempt < max_attempts - 1:
                delay = base_delay * (2 ** attempt)
                print(f"Límite de tasa, reintentando en {delay}s...")
                time.sleep(delay)
            else:
                raise
        except CloreAPIError as e:
            if e.code in (1,):  # Los errores de BD pueden ser transitorios
                if attempt < max_attempts - 1:
                    time.sleep(base_delay)
                    continue
            raise

# Uso
client = CloreAI()
servers = retry_operation(lambda: client.marketplace(gpu="RTX 4090"))
```

***

## Consejos de rendimiento

### 1. Reutiliza el cliente

```python
# ❌ Malo — crea una conexión HTTP nueva cada vez
for _ in range(10):
    client = CloreAI()
    client.marketplace()
    client.close()

# ✅ Bueno — reutiliza la conexión HTTP
client = CloreAI()
for _ in range(10):
    client.marketplace()
client.close()
```

### 2. Usa asincronía para operaciones concurrentes

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        # Ejecutar 3 búsquedas concurrentes
        rtx4090, rtx3090, a100 = await asyncio.gather(
            client.marketplace(gpu="RTX 4090"),
            client.marketplace(gpu="RTX 3090"),
            client.marketplace(gpu="A100"),
        )

        print(f"RTX 4090: {len(rtx4090)} servidores")
        print(f"RTX 3090: {len(rtx3090)} servidores")
        print(f"A100: {len(a100)} servidores")

asyncio.run(compare_gpus())
```

### 3. Creación de órdenes por lotes asincrónica

```python
import asyncio
from clore_ai import AsyncCloreAI

async def batch_deploy(server_ids):
    async with AsyncCloreAI() as client:
        tasks = [
            client.create_order(
                server_id=sid,
                image="cloreai/ubuntu22.04-cuda12",
                type="on-demand",
                currency="bitcoin",
                ssh_password="BatchPass123",
                ports={"22": "tcp"}
            )
            for sid in server_ids
        ]
        orders = await asyncio.gather(*tasks, return_exceptions=True)

        for sid, result in zip(server_ids, orders):
            if isinstance(result, Exception):
                print(f"Servidor {sid}: FALLÓ — {result}")
            else:
                print(f"Servidor {sid}: Orden {result.id} creada")

        return orders

# Desplegar en 3 servidores a la vez
asyncio.run(batch_deploy([142, 305, 891]))
```

{% hint style="warning" %}
**Nota:** El SDK aplica un enfriamiento de 5 segundos entre `create_order` llamadas. Incluso en modo asincrónico, las órdenes se espacian para respetar los límites de tasa.
{% endhint %}

### 4. Cierra los clientes cuando termines

```python
# El gestor de contexto se encarga de esto automáticamente
with CloreAI() as client:
    # trabajo...
    pass  # client.close() llamado automáticamente

# O cierra manualmente
client = CloreAI()
try:
    # trabajo...
    pass
finally:
    client.close()
```

***

## Ejemplo completo: Autoescalar trabajadores GPU

```python
import asyncio
import time
from clore_ai import AsyncCloreAI
from clore_ai.exceptions import CloreAPIError

async def auto_scale(
    gpu_model="RTX 4090",
    max_price=2.0,
    target_workers=3,
    image="cloreai/ubuntu22.04-cuda12"
):
    """Mantener un grupo de trabajadores GPU."""
    async with AsyncCloreAI() as client:
        # 1. Comprobar órdenes actuales
        current_orders = await client.my_orders()
        active_count = len(current_orders)
        print(f"Trabajadores activos: {active_count}/{target_workers}")

        if active_count >= target_workers:
            print("Ya en el objetivo. Nada que hacer.")
            return

        # 2. Encontrar servidores disponibles
        servers = await client.marketplace(gpu=gpu_model, max_price_usd=max_price)
        servers.sort(key=lambda s: s.price_usd or float("inf"))

        needed = target_workers - active_count
        candidates = servers[:needed]

        if len(candidates) < needed:
            print(f"Only {len(candidates)} servers available (need {needed})")

        # 3. Deploy
        for server in candidates:
            try:
                order = await client.create_order(
                    server_id=server.id,
                    image=image,
                    type="on-demand",
                    currency="bitcoin",
                    ssh_password="WorkerPass123",
                    ports={"22": "tcp"}
                )
                print(f"Deployed on server {server.id} → order {order.id}")
            except CloreAPIError as e:
                print(f"Failed to deploy on {server.id}: {e}")

asyncio.run(auto_scale())
```

***

## Siguientes pasos

* [Automatización CLI](/guides/guides_v2-es/avanzado/cli-automation.md) — Scripts Bash, CI/CD, operaciones por lotes
* [Procesamiento por lotes](/guides/guides_v2-es/avanzado/batch-processing.md) — Procesar grandes cargas de trabajo en GPUs de Clore
* [Integración de API](/guides/guides_v2-es/avanzado/api-integration.md) — Conectar servicios de IA a tus aplicaciones


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-es/avanzado/python-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
