typing.LiteralString representa strings consideradas literais ou derivadas apenas de outras strings literais. O objetivo é ajudar APIs sensíveis a distinguir texto definido pelo desenvolvedor de conteúdo dinâmico recebido de usuários, arquivos, rede ou banco de dados. Essa distinção pode reduzir riscos de injeção em SQL, comandos, templates e formatos, mas não substitui parametrização, escaping, validação ou autorização.
Neste guia, você aprenderá como LiteralString é inferido, quais operações preservam confiança, como projetar APIs, onde o recurso encontra limites, como combiná-lo com parâmetros SQL e como evitar uma falsa sensação de segurança.
O problema de strings dinâmicas
def executar_sql(query: str) -> None:
...
nome = input("Nome: ")
executar_sql(f"SELECT * FROM usuarios WHERE nome = '{nome}'")A assinatura aceita qualquer string. O analisador não distingue a consulta fixa de uma consulta construída com entrada externa. Em runtime, o exemplo é vulnerável a injeção.
Declarando LiteralString
from typing import LiteralString
def executar_sql(query: LiteralString) -> None:
...
executar_sql("SELECT * FROM usuarios")Uma string escrita diretamente no código é compatível. Uma variável ampla do tipo str normalmente não é.
Entrada externa deve ser rejeitada
query: str = input("SQL: ")
executar_sql(query) # erro estático esperadoO analisador sinaliza que uma string arbitrária não atende ao contrato. Isso cria uma barreira útil em revisão e CI.
LiteralString é um subtipo de str
Uma LiteralString pode ser usada onde str é esperado. O contrário não é verdadeiro. Funções comuns de leitura e exibição continuam aceitando o valor normalmente.
Concatenação de literais
prefixo: LiteralString = "SELECT id, nome "
sufixo: LiteralString = "FROM usuarios"
query = prefixo + sufixo
executar_sql(query)Operações que combinam apenas LiteralStrings podem preservar o tipo confiável. A inferência exata depende do analisador e da expressão.
f-strings
tabela: LiteralString = "usuarios"
query = f"SELECT * FROM {tabela}"Uma f-string pode permanecer LiteralString quando todas as partes interpoladas também são confiáveis. Se qualquer expressão for apenas str, a confiança deve ser perdida.
Não use interpolação para dados SQL
Mesmo uma API com LiteralString deve separar estrutura e valores:
def consultar(query: LiteralString, parametros: tuple[object, ...]) -> None:
...
nome = input("Nome: ")
consultar(
"SELECT * FROM usuarios WHERE nome = ?",
(nome,),
)A consulta é literal e o dado externo é enviado como parâmetro. O driver é responsável por encoding e escaping. Essa é a proteção principal.
Nomes de tabela dinâmicos
Drivers geralmente parametrizam valores, não identificadores SQL. Se a tabela precisa variar, escolha-a a partir de um conjunto fechado:
from typing import Literal
Tabela = Literal["usuarios", "pedidos"]
def selecionar_tabela(tabela: Tabela) -> LiteralString:
if tabela == "usuarios":
return "usuarios"
return "pedidos"Não transforme uma string arbitrária em LiteralString com cast. Valide contra allowlists e produza literais conhecidos.
Comandos de shell
def executar_comando(comando: LiteralString) -> None:
...O tipo pode desencorajar comandos construídos com entrada externa, mas a melhor prática é evitar shell=True e passar uma lista de argumentos ao subprocesso:
subprocess.run(["git", "show", revisao], check=True)A lista separa programa e argumentos. LiteralString não torna o shell seguro automaticamente.
Templates HTML
Uma função de renderização pode exigir template literal e receber dados separadamente:
def renderizar(template: LiteralString, contexto: dict[str, object]) -> str:
...O engine ainda precisa escapar valores por contexto. HTML, atributos, JavaScript e URLs possuem regras diferentes. LiteralString protege a origem do template, não o conteúdo renderizado.
Format strings
def logar(formato: LiteralString, *args: object) -> None:
...
logar("usuário %s entrou", nome)Manter o formato como literal ajuda ferramentas de logging e reduz problemas com placeholders controlados externamente. Os argumentos continuam separados.
Internacionalização
Textos traduzidos carregados de catálogos são str, não LiteralString, mesmo que o catálogo seja controlado pelo projeto. Não force o tipo apenas para satisfazer a assinatura. APIs de tradução e templates precisam de outro modelo de confiança.
Arquivos de configuração
Uma string lida de YAML, JSON ou variável de ambiente é dinâmica. Ela não se torna confiável porque o arquivo está no repositório. Em alguns ambientes, configuração pode ser alterada por deploy, operadores ou atacantes.
Funções que preservam confiança
def adicionar_limite(query: LiteralString) -> LiteralString:
return query + " LIMIT 100"Uma função pode receber e retornar LiteralString quando só adiciona conteúdo literal. Se ela incorpora um str arbitrário, o retorno deve ser str.
Builders seguros
Um builder pode representar fragmentos aprovados:
def ordenar_por_nome(query: LiteralString) -> LiteralString:
return query + " ORDER BY nome"Para opções dinâmicas, mapeie enums ou Literals para fragmentos fixos. Não aceite fragmentos livres.
cast não sanitiza
from typing import cast
entrada = input("SQL: ")
query = cast(LiteralString, entrada)O cast apenas silencia o analisador. Ele não valida, escapa nem altera o valor. Usá-lo sobre entrada não confiável destrói a garantia do contrato.
Validação não cria automaticamente LiteralString
Mesmo após verificar uma regex ou allowlist, o analisador pode continuar vendo str. Uma função pequena pode retornar um Literal fechado ou escolher explicitamente um literal. Evite funções genéricas que prometem transformar qualquer texto em confiável.
Literal e LiteralString
Literal["asc", "desc"] representa valores exatos de um conjunto. Esses valores são compatíveis com LiteralString. Literal é melhor para opções fechadas; LiteralString é melhor para APIs que aceitam qualquer texto construído exclusivamente a partir de literais confiáveis.
TypeGuard e LiteralString
TypeGuard não consegue provar de forma geral que uma string arbitrária “veio de literal”. A origem é uma propriedade do fluxo estático, não algo observável no conteúdo. Duas strings iguais podem ter origens diferentes.
Dados de bibliotecas externas
Retornos anotados como str perdem confiança, mesmo que a implementação retorne constantes. Uma biblioteca pode declarar LiteralString quando sua API realmente preserva a origem, mas deve garantir esse contrato em todas as versões.
Stubs
Bibliotecas de banco, logging e templates podem usar LiteralString em arquivos .pyi. Teste a experiência do consumidor com mypy e pyright. Uma anotação excessivamente restritiva pode tornar usos legítimos impossíveis.
Não é rastreamento completo de taint
LiteralString oferece uma aproximação simples de confiança. Ele não rastreia fontes, sanitizadores, contextos, codificações ou fluxos entre processos como um sistema completo de taint analysis.
Segurança em camadas
Use LiteralString como uma camada adicional: parâmetros SQL, listas de argumentos, escaping contextual, allowlists, privilégios mínimos, validação e testes continuam necessários. O tipo ajuda a prevenir erros antes do runtime, não substitui controles reais.
Compatibilidade
Use typing_extensions.LiteralString em versões anteriores. O suporte e a precisão dependem do analisador. Mantenha versões atualizadas e adicione casos de tipagem ao CI.
Erros comuns
- Usar cast em entrada externa: não há sanitização.
- Interpolar valores SQL: use parâmetros do driver.
- Assumir que shell ficou seguro: evite shell=True.
- Tratar configuração como literal: é dado dinâmico.
- Confundir com taint analysis completo: o modelo é limitado.
- Confiar apenas no tipo: runtime precisa de defesas.
Exemplo completo: repositório SQL
from typing import Literal, LiteralString
Ordem = Literal["nome", "criado_em"]
def coluna_ordem(ordem: Ordem) -> LiteralString:
if ordem == "nome":
return "nome"
return "criado_em"
def listar_usuarios(
conexao,
termo: str,
ordem: Ordem,
) -> list[tuple[object, ...]]:
coluna = coluna_ordem(ordem)
query: LiteralString
if coluna == "nome":
query = (
"SELECT id, nome FROM usuarios "
"WHERE nome LIKE ? ORDER BY nome"
)
else:
query = (
"SELECT id, nome FROM usuarios "
"WHERE nome LIKE ? ORDER BY criado_em"
)
cursor = conexao.execute(query, (f"%{termo}%",))
return cursor.fetchall()Os valores de busca são parametrizados. A coluna é escolhida por uma união Literal e cada consulta final é escrita como literal. Nenhuma entrada externa vira fragmento SQL.
Testes estáticos
from typing import assert_type
assert_type(coluna_ordem("nome"), LiteralString)
texto: str = input()
# executar_sql(texto) deve falhar na análiseInclua casos válidos e inválidos para proteger a fronteira durante mudanças de stubs e analisadores.
Quando usar
Use LiteralString em APIs onde a estrutura textual precisa ser definida pelo desenvolvedor: consultas, templates, formatos e expressões. Não use em funções comuns que processam texto arbitrário, pois isso criaria atrito sem benefício.
Conclusão
LiteralString permite declarar que uma API espera texto originado de literais confiáveis. Ele ajuda o analisador a bloquear strings dinâmicas em posições sensíveis e incentiva a separação entre estrutura e dados.
A documentação oficial de LiteralString no módulo typing detalha a inferência. Use-o como defesa adicional, nunca como substituto de queries parametrizadas, escaping, allowlists, validação e princípios de menor privilégio.







