> For the complete documentation index, see [llms.txt](https://docs.plenit.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.plenit.com/docs-pt/referencia/cliente-sdk/cliente-sdk-python.md).

# Cliente SDK Python

A secção atual detalha o cliente SDK Python da Jotelulu para o acesso simplificado à API Pública da Jotelulu.

### 📖 Documentação da API

#### Endpoints Principais

| Serviço              | Endpoint                                              | Descrição                    |
| -------------------- | ----------------------------------------------------- | ---------------------------- |
| **Núcleo**           | `/core/v1/organizations`                              | Gestão de organizações       |
| **Núcleo**           | `/core/v1/organizations/{id}/users`                   | Gestão de utilizadores       |
| **Servidores**       | `/servers/v1/subscriptions/{id}/instances`            | Instâncias de servidor       |
| **Servidores**       | `/servers/v1/subscriptions/{id}/networks`             | Gestão de redes              |
| **Desktops Remotos** | `/remote-desktops/v1/subscriptions/{id}/instances`    | Instâncias de Desktop Remoto |
| **Desktops Remotos** | `/remote-desktops/v1/subscriptions/{id}/applications` | Aplicações                   |

#### Modelos Principais

* **Organização**: Representa uma organização
* **Utilizador**: Utilizador do sistema
* **Subscrição**: Subscrição a serviços
* **Instância**: Instância de servidor ou Desktop Remoto
* **Aplicação**: Aplicação instalável
* **NetworkInterface**: Interface de rede
* **FirewallInboundRule**: Regra de firewall

#### 🚀 Instalação em Ambiente Virtual (Recomendado)

```bash
# Criar ambiente virtual
python -m venv jotelulu-env

# Ativar ambiente virtual
# No Windows:
jotelulu-env\Scripts\activate
# No Linux/macOS:
source jotelulu-env/bin/activate

# Instalar o SDK
pip install generated-client-python
```

### 🔐 Autenticação com JWT

#### Configuração Básica

```python
import openapi_client
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configurar o cliente com JWT
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",  # Produção
    access_token="your_jwt_token_here"
)

# Criar cliente API
api_client = ApiClient(configuration)
```

#### Configuração de Ambientes

```python
# Ambientes disponíveis
ENVIRONMENTS = {
    'production': 'https://connect-eu1.jotelulu.com'
}

# Configuração por ambiente
def create_jotelulu_client(environment='production', token=None):
    configuration = Configuration(
        host=ENVIRONMENTS[environment],
        access_token=token
    )
    return ApiClient(configuration)
```

#### Gestão Segura de Tokens

```python
import os
from openapi_client import Configuration, ApiClient

class JoteluluClient:
    def __init__(self, environment='production'):
        # Obter token a partir de variáveis de ambiente
        token = os.getenv('JOTELULU_JWT_TOKEN')
        if not token:
            raise ValueError("A variável de ambiente JOTELULU_JWT_TOKEN é obrigatória")
        
        self.configuration = Configuration(
            host=ENVIRONMENTS.get(environment, ENVIRONMENTS['production']),
            access_token=token
        )
        self.api_client = ApiClient(self.configuration)
    
    def get_organizations_api(self):
        from openapi_client.api.organizations_api import OrganizationsApi
        return OrganizationsApi(self.api_client)
    
    def get_instances_api(self):
        from openapi_client.api.instances_api import InstancesApi
        return InstancesApi(self.api_client)
```

### 🐍 Integração com Django

#### 1. Instalação no Projeto Django

```bash
# Criar novo projeto Django
django-admin startproject mi_projeto_jotelulu
cd mi_projeto_jotelulu

# Criar aplicação
python manage.py startapp jotelulu_integration

# Instalar dependências
pip install django
pip install generated-client-python
```

#### 2. Configuração em settings.py

```python
# settings.py
import os

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'jotelulu_integration',  # A tua aplicação
]

# Configuração Jotelulu
JOTELULU_SETTINGS = {
    'ENVIRONMENT': os.getenv('JOTELULU_ENV', 'production'),
    'JWT_TOKEN': os.getenv('JOTELULU_JWT_TOKEN'),
    'API_ENDPOINTS': {
        'production': 'https://connect-eu1.jotelulu.com'
    }
}
```

#### 3. Serviço Django para Jotelulu

```python
# jotelulu_integration/services.py
from django.conf import settings
import openapi_client
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.exceptions import ApiException

class JoteluluService:
    def __init__(self):
        jotelulu_config = settings.JOTELULU_SETTINGS
        environment = jotelulu_config['ENVIRONMENT']
        
        self.configuration = openapi_client.Configuration(
            host=jotelulu_config['API_ENDPOINTS'][environment],
            access_token=jotelulu_config['JWT_TOKEN']
        )
        self.api_client = openapi_client.ApiClient(self.configuration)
    
    def get_user_organizations(self):
        """Obter organizações do utilizador atual"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_user_organizations()
            return response.data
        except ApiException as e:
            print(f"Erro ao obter organizações: {e}")
            return []
    
    def get_organization_users(self, organization_id):
        """Obter utilizadores de uma organização"""
        try:
            api_instance = OrganizationsApi(self.api_client)
            response = api_instance.list_organization_users(organization_id)
            return response.data
        except ApiException as e:
            print(f"Erro ao obter utilizadores: {e}")
            return []
    
    def get_remote_desktop_instances(self, subscription_id):
        """Obter instâncias de Desktop Remoto"""
        try:
            api_instance = InstancesApi(self.api_client)
            response = api_instance.list_remote_desktop_subscription_instances(subscription_id)
            return response.data
        except ApiException as e:
            print(f"Erro ao obter instâncias: {e}")
            return []
```

#### 4. Vistas Django

```python
# jotelulu_integration/views.py
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .services import JoteluluService

def dashboard(request):
    """Vista principal do dashboard"""
    jotelulu = JoteluluService()
    organizations = jotelulu.get_user_organizations()
    
    context = {
        'organizations': organizations,
        'user': request.user
    }
    return render(request, 'jotelulu_integration/dashboard.html', context)

def organization_detail(request, organization_id):
    """Detalhe de uma organização"""
    jotelulu = JoteluluService()
    users = jotelulu.get_organization_users(organization_id)
    
    return JsonResponse({
        'organization_id': organization_id,
        'users': [{'id': user.id, 'name': user.name, 'email': user.email} for user in users]
    })

@csrf_exempt
def create_organization_user(request, organization_id):
    """Criar utilizador na organização"""
    if request.method == 'POST':
        import json
        from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest
        from openapi_client.model.create_organization_user_request_data import CreateOrganizationUserRequestData
        
        data = json.loads(request.body)
        jotelulu = JoteluluService()
        
        try:
            api_instance = jotelulu.get_organizations_api()
            user_request = CreateOrganizationUserRequest(
                data=CreateOrganizationUserRequestData(
                    email=data['email'],
                    name=data['name'],
                    last_name=data['lastName'],
                    password=data.get('password')
                )
            )
            
            response = api_instance.create_organization_user(organization_id, user_request)
            return JsonResponse({'success': True, 'user_id': response.data.user_id})
            
        except Exception as e:
            return JsonResponse({'success': False, 'error': str(e)})
    
    return JsonResponse({'error': 'Método não permitido'}, status=405)
```

#### 5. URLs Django

```python
# jotelulu_integration/urls.py
from django.urls import path
from . import views

app_name = 'jotelulu_integration'

urlpatterns = [
    path('', views.dashboard, name='dashboard'),
    path('organization/<str:organization_id>/', views.organization_detail, name='organization_detail'),
    path('organization/<str:organization_id>/users/', views.create_organization_user, name='create_user'),
]

# urls.py principal do projeto
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('jotelulu/', include('jotelulu_integration.urls')),
]
```

### 📚 Exemplos de Utilização

#### Gestão de Organizações

```python
from openapi_client.api.organizations_api import OrganizationsApi
from openapi_client.model.create_organization_user_request import CreateOrganizationUserRequest

# Inicializar cliente
client = JoteluluClient()
org_api = client.get_organizations_api()

# Listar organizações do utilizador
organizations = org_api.list_user_organizations()
print(f"Organizações encontradas: {len(organizations.data)}")

# Listar utilizadores de uma organização
org_id = "ORG-123"
users = org_api.list_organization_users(org_id)
for user in users.data:
    print(f"Utilizador: {user.name} ({user.email})")

# Criar novo utilizador
new_user_request = CreateOrganizationUserRequest(
    data={
        'email': 'novo@ejemplo.com',
        'name': 'Novo',
        'lastName': 'Utilizador',
        'password': 'password123',
        'twoFactorAuthRequired': True
    }
)
result = org_api.create_organization_user(org_id, new_user_request)
print(f"Utilizador criado com ID: {result.data.user_id}")
```

#### Gestão de Instâncias e Servidores

```python
from openapi_client.api.instances_api import InstancesApi
from openapi_client.api.instance_api import InstanceApi

# APIs de instâncias
instances_api = InstancesApi(api_client)
instance_api = InstanceApi(api_client)

# Listar instâncias de Desktop Remoto
subscription_id = "SUB-456"
rd_instances = instances_api.list_remote_desktop_subscription_instances(subscription_id)

for instance in rd_instances.data:
    print(f"Instância: {instance.name} - Estado: {instance.state}")
    
    # Obter progresso de implementação
    if instance.state == 'creating':
        progress = instance_api.get_instance_progress(subscription_id, instance.id)
        print(f"Progresso: {progress.data.progress}")

# Listar instâncias de servidor
server_instances = instance_api.list_server_subscription_instances(subscription_id)
print(f"Instâncias de servidor: {len(server_instances.data)}")
```

#### Gestão de Aplicações

```python
from openapi_client.api.applications_api import ApplicationsApi
from openapi_client.model.create_instance_application_request import CreateInstanceApplicationRequest

apps_api = ApplicationsApi(api_client)

# Listar aplicações disponíveis
available_apps = apps_api.list_applications()
print("Aplicações disponíveis:")
for app in available_apps.data:
    print(f"- {app.name} (ID: {app.id})")

# Listar aplicações de uma subscrição
subscription_apps = apps_api.list_subscription_applications(subscription_id)
print(f"Aplicações instaladas: {len(subscription_apps.data)}")

# Instalar nova aplicação
install_request = CreateInstanceApplicationRequest(
    data={
        'instanceId': 'INST-789',
        'applicationId': 'APP-123'
    }
)
result = apps_api.create_instance_application(subscription_id, install_request)
print(f"Aplicação instalada: {result.data[0].id}")
```

#### Gestão de Redes e Firewall

```python
from openapi_client.api.networks_api import NetworksApi
from openapi_client.api.firewall_api import FirewallApi
from openapi_client.model.create_isolated_network_request import CreateIsolatedNetworkRequest
from openapi_client.model.create_firewall_inbound_rule_request import CreateFirewallInboundRuleRequest

networks_api = NetworksApi(api_client)
firewall_api = FirewallApi(api_client)

# Criar rede isolada
network_request = CreateIsolatedNetworkRequest(
    data={
        'name': 'Minha Rede privada',
        'network': '192.168.100.0',
        'netmask': '255.255.255.0',
        'gateway': '192.168.100.1'
    }
)
network = networks_api.create_isolated_network(subscription_id, network_request)
print(f"Rede criada: {network.data.name}")

# Criar regra de firewall
firewall_rule = CreateFirewallInboundRuleRequest(
    data={
        'protocol': 'TCP',
        'publicNetworkId': 'NET-456',
        'privateNicAddressIpId': 'NIC-789',
        'publicPort': 80,
        'privatePort': 8080,
        'origin': '0.0.0.0/0'
    }
)
rule = firewall_api.create_firewall_inbound_rule(
    subscription_id, 
    network.data.main_network_id, 
    firewall_rule
)
print(f"Regra de firewall criada: {rule.data.id}")
```

### 🛠️ Gestão de Erros

```python
from openapi_client.exceptions import ApiException
import logging

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def safe_api_call(api_function, *args, **kwargs):
    """Wrapper para chamadas seguras à API"""
    try:
        return api_function(*args, **kwargs)
    except ApiException as e:
        logger.error(f"Erro da API: {e.status} - {e.reason}")
        logger.error(f"Corpo da resposta: {e.body}")
        
        # Gestão específica por código de erro
        if e.status == 401:
            logger.error("Token JWT inválido ou expirado")
        elif e.status == 403:
            logger.error("Permissões insuficientes")
        elif e.status == 404:
            logger.error("Recurso não encontrado")
        elif e.status >= 500:
            logger.error("Erro interno do servidor")
        
        return None
    except Exception as e:
        logger.error(f"Erro inesperado: {str(e)}")
        return None

# Exemplo de utilização
organizations = safe_api_call(org_api.list_user_organizations)
if organizations:
    print(f"Organizações obtidas: {len(organizations.data)}")
else:
    print("Não foi possível obter as organizações")
```

### 🔧 Configuração Avançada

#### Cliente Personalizado com Retry

```python
import time
from functools import wraps

def retry_on_failure(max_retries=3, delay=1):
    """Decorador para tentar novamente chamadas falhadas"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except ApiException as e:
                    if e.status >= 500 and attempt < max_retries - 1:
                        time.sleep(delay * (2 ** attempt))  # Backoff exponencial
                        continue
                    raise
            return None
        return wrapper
    return decorator

class RobustJoteluluClient(JoteluluClient):
    @retry_on_failure(max_retries=3)
    def get_organizations_with_retry(self):
        api = self.get_organizations_api()
        return api.list_user_organizations()
```

#### Configuração de Timeout

```python
from openapi_client.configuration import Configuration
from openapi_client.api_client import ApiClient

# Configuração com timeouts personalizados
configuration = Configuration(
    host="https://connect-eu1.jotelulu.com",
    access_token="your_token"
)

# Configurar timeouts
api_client = ApiClient(configuration)
api_client.rest_client.pool_manager.connection_pool_kw['timeout'] = 30
```

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.plenit.com/docs-pt/referencia/cliente-sdk/cliente-sdk-python.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
