Introdução
Um jantar comunitário precisa de um convite para cada pessoa. Você transformará uma lista CSV de convidados, dados JSON do evento e um modelo HTML pronto em um novo lote de arquivos personalizados. Duas pessoas com o mesmo nome devem receber arquivos separados, e nomes como Zoë, "Z" & <Friends> devem aparecer como texto.
É necessário conhecer strings, dicionários, laços e funções Python, além do uso básico do terminal. Este laboratório explica leitura de arquivos, registros CSV, modelos, escape de texto HTML e novos diretórios de saída. A interface de linha de comando e o visual estão prontos; você implementará o mapeamento dos campos, a validação completa e a geração por convidado. Não criará CSS, enviará e-mails nem produzirá PDFs. Estimativa do autor: 55–75 minutos, incluindo leitura e erros comuns.
Abra seu primeiro convite
Nesta etapa, você gerará um convite normal e relacionará a página aos campos de entrada. Em WebIDE, abra Terminal → New Terminal e espere o prompt do shell de labex. Em uma janela estreita, Terminal pode estar em More (...). Execute todos os comandos nesse terminal. O espaço preparado é independente dos laboratórios anteriores.
cd altera o diretório atual. Um caminho absoluto começa com /; caminhos relativos são interpretados a partir do diretório atual. Se um comando não encontrar um arquivo depois de uma mudança de diretório, volte aqui com o mesmo comando:
cd /home/labex/project/invitations
cat mostra um arquivo de texto. Leia os dados do evento:
cat event.json
JSON usa chaves para objetos e aspas duplas para chaves e valores de string. Esses quatro valores se tornam título, data, hora e local do convite. O exemplo executável one.py já mapeia esses dados e um convidado para o design fornecido. python3 executa um arquivo Python. Gere o exemplo no novo diretório batches/first:
python3 one.py
Saída esperada:
Created batch: batches/first
Mude da aba da plataforma WebIDE para Web 8080. A preparação já iniciou o visualizador de convites; não é necessário usar Go Live. Atualize a prévia e encontre first/g001.html. Clique em Open invitation para abrir o arquivo HTML real em uma nova aba do navegador. Leia Maya Patel, The Long Table, a hora e o local. Feche essa aba do navegador para voltar a Web 8080. Download HTML salva o mesmo arquivo para uso fora do laboratório.

A saudação identifica a pessoa; as duas áreas inferiores mostram a hora e o local do evento.
A prévia lê apenas arquivos que realmente existem em batches. Não gera convites ausentes. O antigo batches/archive permanece como trabalho anterior. Seu programa final criará novos lotes, preservando esses arquivos e todas as entradas. Volte a WebIDE para a próxima etapa.
Mapeie registros CSV para texto HTML
Nesta etapa, você fará o renderizador reutilizável colocar cada valor do convidado na área de texto correta. Volte ao mesmo terminal e espaço de WebIDE; os caminhos relativos abaixo partem de /home/labex/project/invitations.
Leia registros em vez de dividir por vírgulas
CSV é um formato de tabela. A primeira linha contém os nomes dos campos. Uma vírgula separa campos, exceto dentro de um campo entre aspas; uma aspa dentro desse campo é duplicada. Portanto, split(",") não consegue ler todas as linhas corretamente. Leia a lista fornecida:
cat guests.csv
Existem dois registros de Alex Chen com valores guest_id diferentes. O nome do último convidado contém uma vírgula, aspas, & e sinais de menor e maior. Todos são caracteres válidos em um nome. O ID estável identifica uma pessoa; o nome exibido não basta.
O arquivo pronto generate.py lê esses registros. Leia-o antes de modificar o renderizador:
cat generate.py
with ... as source abre um arquivo e o fecha ao terminar o bloco indentado. encoding="utf-8" preserva caracteres Unicode. newline="" permite ao leitor CSV cuidar dos limites dos registros. csv.DictReader(source) usa o cabeçalho para criar um dicionário por registro; list(...) reúne os registros em uma lista. Por exemplo, guest["name"] contém o nome completo, incluindo uma vírgula que faça parte dele. json.loads(...) transforma o texto JSON do evento em um dicionário. Esses leitores e a interface de comando já estão implementados.
Separe as aspas CSV do escape HTML
Uma página HTML usa <...> para tags e &...; para referências de caracteres. As aspas CSV protegem a estrutura da tabela, mas não impedem que um nome como <Friends> seja interpretado como HTML. html.escape(value, quote=True) converte caracteres especiais em referências que o navegador exibe como o texto original. Aqui todos os valores entram em áreas de texto HTML. Isso não é uma receita geral para inserir dados em JavaScript, CSS ou URLs arbitrários.
Template usa $name, $title e os demais marcadores $field de invitation.html. substitute(mapping) troca cada marcador pelo valor correspondente do dicionário e rejeita mapeamentos ausentes. Os seis marcadores do corpo, data-field="name", message, title, date, time e venue, identificam as áreas semânticas de texto. Preserve uma de cada tipo com seu texto correspondente. Não modifique o modelo de entrada fornecido. Tags, disposição e grafias de entidades equivalentes são aceitas na saída gerada; um nome apenas no <title> da página não preenche sua área no corpo.
Crie o renderizador abaixo. cat > rendering.py escreve o arquivo, substituindo o conteúdo anterior. O delimitador entre aspas do bloco here-document <<'PYCODE' trata tudo até o fechamento PYCODE como texto literal. O marcador final deve ocupar uma linha própria. Somente o renderizador é substituído; as entradas continuam intactas.
O dicionário mapeia explicitamente os campos do convidado e do evento. values.items() produz pares chave/valor. A compreensão de dicionário {key: ... for key, value in values.items()} aplica escape a cada valor e conserva sua chave. from html import escape importa a função da biblioteca padrão.
cat > rendering.py <<'PYCODE'
from html import escape
def render_guest(guest, event, template):
values = {"name": guest["name"], "message": guest["message"],
"title": event["title"], "date": event["date"],
"time": event["time"], "venue": event["venue"]}
return template.substitute({key: escape(value, quote=True)
for key, value in values.items()})
PYCODE
A função retorna texto HTML; ela não cria arquivos. Módulos auxiliares locais podem ser arquivos .py no mesmo nível de generate.py; subdiretórios de pacotes estão fora deste exercício. O escape ocorre uma única vez antes da substituição, para o navegador mostrar & em vez do texto visível &. Agora o design do convite está separado do texto personalizado.
Valide todos antes de criar um lote
Nesta etapa, você gerará um arquivo por ID e protegerá o trabalho anterior. Continue no mesmo terminal de WebIDE.
O batch.py original usa nomes de pessoas como nomes de arquivo e permite um diretório existente. Duas pessoas chamadas Alex Chen podem sobrescrever os arquivos uma da outra. Leia essa pequena função:
cat batch.py
O contrato exige um novo diretório de lote, contendo <guest_id>.html para cada linha. IDs só podem usar letras ASCII minúsculas, dígitos e hífens. Nomes e mensagens continuam sendo texto UTF-8 sem essa restrição. Os campos obrigatórios do convidado são guest_id, name, message; os do evento são title, date, time, venue. Um valor obrigatório vazio ou só com espaços em branco, um ID repetido ou um diretório de saída existente devem ser rejeitados com um erro útil. Preserve o CSV, os dois arquivos de evento, o modelo e batches/archive.
Separe a preparação da escrita
Primeiro valide cada registro e renderize todas as páginas. Só depois de concluir essa fase crie o diretório e escreva os arquivos. Caso contrário, um último registro incorreto poderia deixar um lote parcial enganoso. Isso protege dos erros de entrada ensinados aqui; não oferece garantia transacional contra falha de disco ou queda repentina de energia.
isinstance(value, str) verifica se um valor é texto. event.get(field) retorna None quando falta uma chave, permitindo informar um erro útil. strip() verifica se há conteúdo além de espaços em branco, sem alterar o texto que será renderizado. raise ValueError(...) sinaliza uma entrada inválida; a interface pronta captura o erro, imprime Cannot generate invitations: ... e termina com falha.
Um set() guarda os IDs já vistos. in testa a presença e seen.add(...) registra um ID. all(...) verifica se cada caractere satisfaz a regra informada. pages.append((guest_id, page)) guarda um par ID/página. Por fim, for guest_id, page in pages desempacota esses pares.
Path(output) representa o diretório solicitado. mkdir() cria esse diretório e rejeita um caminho existente porque não passamos exist_ok=True. O operador / une caminhos. Uma f-string como f"{guest_id}.html" insere o ID no nome. write_text(..., encoding="utf-8") grava o texto HTML completo em UTF-8.
Substitua somente batch.py pela implementação central abaixo:
cat > batch.py <<'PYCODE'
from pathlib import Path
from rendering import render_guest
def write_batch(guests, event, template, output):
for field in ("title", "date", "time", "venue"):
if not isinstance(event.get(field), str) or not event[field].strip():
raise ValueError(f"Missing event field: {field}")
if not guests:
raise ValueError("The guest list is empty")
seen = set()
pages = []
for guest in guests:
for field in ("guest_id", "name", "message"):
if not isinstance(guest.get(field), str) or not guest[field].strip():
raise ValueError(f"Missing guest field: {field}")
guest_id = guest["guest_id"]
if not all(character in "abcdefghijklmnopqrstuvwxyz0123456789-" for character in guest_id):
raise ValueError("Guest IDs must use lowercase letters, digits, or hyphens")
if guest_id in seen:
raise ValueError(f"Duplicate guest ID: {guest_id}")
seen.add(guest_id)
pages.append((guest_id, render_guest(guest, event, template)))
output = Path(output)
output.mkdir() ## An existing batch is always refused.
for guest_id, page in pages:
(output / f"{guest_id}.html").write_text(page, encoding="utf-8")
PYCODE
from rendering import render_guest utiliza seu renderizador local. O primeiro laço verifica os campos do evento, o próximo valida convidados e prepara páginas, e o último escreve o lote completo. Nenhum arquivo é criado antes de terminar os laços de validação e renderização.
A interface recebe quatro caminhos posicionais nesta ordem: CSV de convidados, JSON do evento, modelo HTML e novo diretório de saída. Execute com a lista completa:
python3 generate.py guests.csv event.json invitation.html batches/evening
Saída esperada:
Created batch: batches/evening
ls lista os itens do diretório:
ls batches/evening
Você deve encontrar g101.html, g102.html e g103.html. Os dois convites de Alex agora têm IDs independentes. Cada arquivo preserva a mensagem da pessoa; o terceiro mantém também a vírgula, as aspas, Unicode e os sinais de menor e maior literais.
Reutilize o gerador e preserve o trabalho anterior
Nesta etapa, você usará o mesmo programa para outro evento e praticará a recuperação de erros comuns. Comece no mesmo terminal de WebIDE. Os comandos são independentes, portanto uma falha esperada não impede a continuação.
Primeiro repita de propósito o caminho de saída já concluído. Isso deve falhar: mkdir() rejeita um lote existente. A mensagem começa com Cannot generate invitations: e inclui um erro de arquivo existente. O lote anterior deve permanecer intacto.
python3 generate.py guests.csv event.json invitation.html batches/evening
Leia o exemplo preparado de ID duplicado. O segundo convidado tem o ID do primeiro:
cat invalid-duplicate.csv
Tente criar um lote com esse arquivo. Essa falha também é esperada e informa Duplicate guest ID: g101 antes da criação de qualquer diretório:
python3 generate.py invalid-duplicate.csv event.json invitation.html batches/rejected-duplicate
O exemplo sem mensagem também falha intencionalmente com Missing guest field: message. Mesmo com registros anteriores válidos, nenhum lote parcial deve aparecer:
python3 generate.py invalid-missing.csv event.json invitation.html batches/rejected-missing
Um caminho digitado incorretamente é um problema diferente: o sistema operacional não consegue abrir o arquivo. O próximo comando usa de propósito guest.csv, que não existe. Leia o erro; não reinicie o ambiente:
python3 generate.py guest.csv daytime.json invitation.html batches/daytime
Recupere-se usando o caminho real guests.csv. Leia o segundo evento e gere um novo lote com o mesmo programa e lista:
cat daytime.json
python3 generate.py guests.csv daytime.json invitation.html batches/daytime
Saída esperada:
Created batch: batches/daytime
Mude para Web 8080 e atualize. Abra evening/g101.html e evening/g102.html separadamente. O nome é igual, mas as mensagens permanecem diferentes. Abra evening/g103.html: vírgula, aspas, ë, e comercial e <Friends> devem aparecer literalmente. Abra daytime/g103.html e leia o novo título, horário e local. Feche a aba do navegador de cada convite para voltar à lista. Use Download HTML no convite diurno e abra o arquivo baixado: o texto personalizado deve corresponder à prévia.

O nome mantém vírgula, aspas e sinais de menor e maior como texto visível na saudação.
Os dois lotes são independentes. CSV, arquivos de evento e modelo não mudaram, e o arquivo anterior continua presente. Entrada inválida, lote existente e caminho de entrada ausente têm explicações diferentes. Você pode corrigir cada caso sem destruir os convites anteriores.
Resumo
Você gerou convites HTML reais e disponíveis para download a partir de registros CSV e dados JSON. Separou IDs estáveis de nomes exibidos, mapeou campos para as áreas de texto do modelo, preservou Unicode e caracteres especiais com UTF-8 e escape HTML e validou o lote inteiro antes de escrever. Agora o gerador pode criar novos lotes de eventos, preservando suas entradas e o trabalho anterior.



