El módulo stringprep expone las tablas definidas por el RFC 3454 para preparar cadenas Unicode utilizadas en protocolos de Internet. La preparación puede mapear o eliminar caracteres, normalizar texto, rechazar categorías prohibidas y aplicar reglas bidireccionales antes de comparar, guardar o transmitir identificadores.
El módulo no ofrece una función universal de limpieza. Proporciona funciones de consulta y mapeo. Cada perfil de protocolo debe definir qué tablas usa, si aplica case folding, qué normalización necesita y cómo trata caracteres no asignados.
Un módulo basado en un estándar histórico
El RFC 3454 se utilizó en perfiles como Nameprep para sistemas tempranos de dominios internacionalizados. Sus tablas se basan en Unicode 3.2, de modo que caracteres modernos pueden aparecer como no asignados en este modelo histórico.
No inventes un perfil para autenticación, dominios o identificadores sensibles. Usa el estándar actual y una biblioteca dedicada cuando exista. stringprep resulta útil para interoperabilidad legada, auditoría y comprensión del proceso.
Tablas expuestas como funciones
Las tablas son demasiado grandes para representarlas como diccionarios normales, por lo que Python ofrece funciones características para conjuntos y funciones de mapeo para transformaciones.
import stringprep
caracter = "\u00ad" # soft hyphen
print(stringprep.in_table_b1(caracter))La tabla B.1 incluye caracteres comúnmente mapeados a nada. La consulta no modifica el texto; la aplicación debe realizar la transformación.
Puntos no asignados
in_table_a1() indica si un punto estaba sin asignar en Unicode 3.2.
def tiene_no_asignado(texto):
return any(stringprep.in_table_a1(c) for c in texto)Un perfil puede prohibirlos para evitar cambios de significado tras futuras asignaciones. Sin embargo, caracteres modernos también pueden ser considerados no asignados por esta tabla histórica.
Mapear caracteres a nada
def aplicar_b1(texto):
return "".join(
c for c in texto
if not stringprep.in_table_b1(c)
)Eliminar caracteres puede hacer que dos entradas distintas converjan. Detecta colisiones después de todo el proceso y conserva el valor original para presentación y auditoría.
Case folding con B.2 y B.3
map_table_b2() proporciona case folding pensado para NFKC. map_table_b3() se usa sin normalización.
def mapear_b2(texto):
return "".join(stringprep.map_table_b2(c) for c in texto)Un mapeo puede devolver varios caracteres. No supongas una relación uno a uno ni valides longitud antes de transformar.
Normalización NFKC
Muchos perfiles históricos aplican NFKC después del mapeo.
import unicodedata
def preparar_base(texto):
texto = aplicar_b1(texto)
texto = mapear_b2(texto)
return unicodedata.normalize("NFKC", texto)NFKC elimina diferencias de compatibilidad y puede cambiar variantes de ancho, caracteres estilísticos y símbolos. Aplícala solo cuando el protocolo lo exija y conserva el original.
Espacios ASCII y no ASCII
Las tablas C.1.1 y C.1.2 identifican espacios ASCII y no ASCII. in_table_c11_c12() comprueba la unión.
espacios = [
c for c in texto
if stringprep.in_table_c11_c12(c)
]Un perfil puede mapearlos, prohibirlos o permitir solo algunos. No sustituyas automáticamente todos por espacio común sin consultar la especificación.
Caracteres de control
C.2.1 y C.2.2 cubren controles ASCII y no ASCII.
def tiene_control(texto):
return any(
stringprep.in_table_c21_c22(c)
for c in texto
)Los controles pueden alterar logs, terminales y framing de protocolos. Escapa caracteres invisibles en diagnósticos aunque el perfil permita algunos.
Uso privado, noncharacters y surrogates
in_table_c3(), in_table_c4() e in_table_c5() identifican uso privado, noncharacters y códigos surrogate.
Recházalos según el perfil y antes de serializar a sistemas que exijan valores escalares Unicode válidos.
Otras categorías prohibidas
C.6 y C.7 marcan caracteres inadecuados para texto plano o representación canónica. C.8 cubre caracteres que cambian propiedades visuales o están obsoletos, y C.9 caracteres de tagging.
Estas categorías muestran por qué strip() más lower() no implementa preparación de protocolos.
Reglas bidireccionales
D.1 identifica caracteres con propiedades R o AL. D.2 identifica caracteres L. Los perfiles RFC 3454 suelen imponer reglas especiales cuando hay texto de derecha a izquierda.
def validar_bidi(texto):
tiene_randal = any(stringprep.in_table_d1(c) for c in texto)
if not tiene_randal:
return True
if any(stringprep.in_table_d2(c) for c in texto):
return False
return (
stringprep.in_table_d1(texto[0])
and stringprep.in_table_d1(texto[-1])
)El ejemplo muestra la regla histórica básica, pero producción debe seguir exactamente el perfil. El orden lógico y visual puede diferir.
Ejemplo orientado a perfil
import unicodedata
class StringPrepError(ValueError):
pass
def preparar_legado(texto):
mapeado = "".join(
"" if stringprep.in_table_b1(c)
else stringprep.map_table_b2(c)
for c in texto
)
normalizado = unicodedata.normalize("NFKC", mapeado)
prohibidas = (
stringprep.in_table_c12,
stringprep.in_table_c21_c22,
stringprep.in_table_c3,
stringprep.in_table_c4,
stringprep.in_table_c5,
stringprep.in_table_c6,
stringprep.in_table_c7,
stringprep.in_table_c8,
stringprep.in_table_c9,
)
for c in normalizado:
if any(tabla(c) for tabla in prohibidas):
raise StringPrepError(
f"carácter prohibido U+{ord(c):04X}"
)
if not validar_bidi(normalizado):
raise StringPrepError("regla bidi inválida")
return normalizadoEs un ejemplo educativo, no un perfil nombrado completo. La lista de tablas, política A.1 y orden deben venir del estándar.
Validar después de transformar
Comprueba límites de caracteres y bytes después del mapeo y la normalización porque la longitud puede cambiar. Si el resultado es una clave única, aplica unicidad transaccional sobre el valor final.
IDNA y dominios
No reconstruyas Nameprep manualmente para dominios modernos. Usa una implementación IDNA acorde al estándar requerido. El procesamiento incluye labels, Punycode y validaciones adicionales.
Contraseñas y usuarios
No apliques stringprep genérico a contraseñas sin especificación. El mapeo o eliminación puede cambiar el secreto y reducir entropía. Protocolos modernos pueden usar perfiles PRECIS u otras reglas.
Para usuarios, conserva el original y una clave preparada separada. Vigila colisiones y caracteres confundibles.
Homógrafos
Case folding y NFKC no unen todos los caracteres visualmente parecidos. Letras latinas, griegas y cirílicas pueden seguir siendo distintas. Identificadores públicos sensibles necesitan políticas de scripts y detección especializada.
Pruebas
Prueba controles, espacios no ASCII, eliminaciones B.1, expansión de case folding, texto RTL, mezcla L/R, entrada vacía y caracteres modernos. Compara con vectores oficiales del protocolo.
Versiona el algoritmo. Cambiarlo para identificadores existentes requiere migración y análisis de colisiones.
Errores frecuentes
- Creer que el módulo ofrece un sanitizer completo.
- Crear un perfil sin estándar.
- Ignorar la base Unicode 3.2.
- Comprobar tamaño antes del mapeo.
- No detectar colisiones.
- Aplicar NFKC o eliminación a contraseñas arbitrariamente.
- Ignorar bidi y homógrafos.
Buenas prácticas
- Usa una biblioteca específica del protocolo.
- Conserva la entrada original.
- Versiona la función de preparación.
- Sigue el orden de la especificación.
- Valida longitud y unicidad al final.
- Escapa caracteres invisibles en logs.
- Prueba vectores oficiales y Unicode adversarial.
Guías relacionadas
Continúa con unicodedata en Python, locale en Python, textwrap en Python, fnmatch en Python y contextvars en Python.
Consulta la documentación oficial de stringprep y el RFC 3454.
Conclusión
stringprep es una caja de herramientas de bajo nivel para perfiles históricos de preparación Unicode. Debe usarse bajo una especificación definida, con conciencia de Unicode 3.2, colisiones, categorías prohibidas y reglas bidi. Para sistemas modernos, prefiere implementaciones mantenidas del protocolo.







