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