MODULO 5.6

๐Ÿ“ฆ Materializacao de Outputs

Definir formatos de saida e implementar geradores.

6
Topicos
~40
Minutos
Pratico
Nivel
Hands-on
Tipo
1

๐Ÿ“Š Definindo Formatos de Saida

# app/services/materializers.py
from enum import Enum
from abc import ABC, abstractmethod

class OutputFormat(Enum):
    TEXT = "text"
    MARKDOWN = "markdown"
    JSON = "json"
    HTML = "html"
    PDF = "pdf"

class BaseMaterializer(ABC):
    @abstractmethod
    async def materialize(self, content: str, metadata: dict) -> bytes:
        pass

    @property
    @abstractmethod
    def format(self) -> OutputFormat:
        pass

    @property
    @abstractmethod
    def content_type(self) -> str:
        pass
2

๐Ÿ”ง Implementando Geradores

class MarkdownMaterializer(BaseMaterializer):
    format = OutputFormat.MARKDOWN
    content_type = "text/markdown"

    async def materialize(self, content: str, metadata: dict) -> bytes:
        # Adiciona cabecalho com metadados
        header = f"""---
request_id: {metadata.get('request_id')}
generated_at: {metadata.get('timestamp')}
persona: {metadata.get('persona')}
---

"""
        return (header + content).encode('utf-8')

class JSONMaterializer(BaseMaterializer):
    format = OutputFormat.JSON
    content_type = "application/json"

    async def materialize(self, content: str, metadata: dict) -> bytes:
        output = {
            "content": content,
            "metadata": metadata
        }
        return json.dumps(output, indent=2, ensure_ascii=False).encode('utf-8')

class HTMLMaterializer(BaseMaterializer):
    format = OutputFormat.HTML
    content_type = "text/html"

    async def materialize(self, content: str, metadata: dict) -> bytes:
        import markdown
        html_content = markdown.markdown(content)
        template = f"""

{html_content}"""
        return template.encode('utf-8')
3

๐Ÿ“ Sistema de Storage

from pathlib import Path
import aiofiles

class ArtifactStorage:
    def __init__(self, base_path: str = "./artifacts"):
        self.base_path = Path(base_path)
        self.base_path.mkdir(parents=True, exist_ok=True)

    async def save(self, request_id: str, content: bytes, format: OutputFormat) -> str:
        """Salva artefato e retorna path"""
        extension = format.value
        filename = f"{request_id}.{extension}"
        filepath = self.base_path / filename

        async with aiofiles.open(filepath, 'wb') as f:
            await f.write(content)

        return str(filepath)

    async def get(self, request_id: str, format: OutputFormat) -> bytes:
        """Recupera artefato salvo"""
        extension = format.value
        filepath = self.base_path / f"{request_id}.{extension}"

        async with aiofiles.open(filepath, 'rb') as f:
            return await f.read()

    async def delete(self, request_id: str, format: OutputFormat):
        """Remove artefato"""
        extension = format.value
        filepath = self.base_path / f"{request_id}.{extension}"
        filepath.unlink(missing_ok=True)
4

๐Ÿ”„ Processamento Assincrono

import asyncio
from concurrent.futures import ThreadPoolExecutor

class AsyncMaterializationService:
    def __init__(self):
        self.executor = ThreadPoolExecutor(max_workers=4)
        self.storage = ArtifactStorage()

    async def materialize_async(
        self,
        content: str,
        format: OutputFormat,
        metadata: dict
    ) -> str:
        """Materializa em background e retorna task_id"""
        task_id = str(uuid4())

        # Inicia em background
        asyncio.create_task(
            self._process(task_id, content, format, metadata)
        )

        return task_id

    async def _process(self, task_id: str, content: str, format: OutputFormat, metadata: dict):
        materializer = self._get_materializer(format)
        artifact = await materializer.materialize(content, metadata)
        await self.storage.save(task_id, artifact, format)

        # Notifica conclusao (webhook, pubsub, etc)
        await self._notify_complete(task_id)
5

โš ๏ธ Tratamento de Falhas

class MaterializationError(Exception):
    pass

class MaterializationService:
    async def materialize_with_fallback(
        self,
        content: str,
        format: OutputFormat,
        metadata: dict
    ) -> tuple[bytes, OutputFormat]:
        """Tenta materializar, com fallback para formato mais simples"""
        try:
            materializer = self._get_materializer(format)
            return await materializer.materialize(content, metadata), format
        except Exception as e:
            self.log.warning(f"Falha em {format}, tentando fallback", error=str(e))

            # Fallback: tenta formato mais simples
            if format == OutputFormat.PDF:
                return await self.materialize_with_fallback(content, OutputFormat.HTML, metadata)
            elif format == OutputFormat.HTML:
                return await self.materialize_with_fallback(content, OutputFormat.MARKDOWN, metadata)
            else:
                # Ultimo recurso: texto puro
                return content.encode('utf-8'), OutputFormat.TEXT
6

๐Ÿงช Testes de Integracao

# tests/test_materialization.py
import pytest

class TestMaterialization:
    @pytest.mark.asyncio
    async def test_markdown_materialization(self):
        materializer = MarkdownMaterializer()
        result = await materializer.materialize(
            "# Titulo\nConteudo",
            {"request_id": "123", "timestamp": "2024-01-01"}
        )
        assert b"request_id: 123" in result
        assert b"# Titulo" in result

    @pytest.mark.asyncio
    async def test_storage_save_and_retrieve(self):
        storage = ArtifactStorage("./test_artifacts")
        content = b"test content"
        await storage.save("test-123", content, OutputFormat.TEXT)
        retrieved = await storage.get("test-123", OutputFormat.TEXT)
        assert retrieved == content

    @pytest.mark.asyncio
    async def test_fallback_on_error(self):
        service = MaterializationService()
        result, format = await service.materialize_with_fallback(
            "content", OutputFormat.PDF, {}
        )
        # Se PDF falhar, deve retornar formato mais simples
        assert format in [OutputFormat.PDF, OutputFormat.HTML, OutputFormat.MARKDOWN, OutputFormat.TEXT]

๐Ÿ“ Resumo do Modulo

โœ“Formatos - Text, Markdown, JSON, HTML
โœ“Storage - Artefatos persistidos
โœ“Async - Processamento em background
โœ“Fallback - Resiliencia a falhas