Offline validation of Argentine identity numbers. Zero dependencies, no network calls.
from ar_validators import validate_cuit
result = validate_cuit("20-00000000-1")
result.valid # True
result.value # "20000000001"
result.metadata["type"] # "persona_fisica_masculino"
bad = validate_cuit("20-00000000-0")
bad.valid # False
bad.errors # ["invalid check digit: expected 1, got 0"]
Argentine identity numbers use checksum algorithms that are easy to get subtly wrong. The CUIT check digit has two special cases that most snippets skip. The CBU carries two independent check digits with different weight tables, and one of those weights is a 9 that gets mistyped as a 1 often enough that a buggy validator still passes casual testing — it only diverges when a specific digit is non-zero.
This library implements them correctly, offline, with no external calls.
| Function | Input | What it checks |
|---|---|---|
validate_cuit |
CUIT / CUIL | 11 digits, known prefix, modulo 11 check digit |
validate_cuil |
CUIL | same, and rejects legal-entity prefixes |
validate_cbu |
CBU | 22 digits, two independent modulo 10 check digits |
validate_dni |
DNI | 7–8 digits, plausible range (no checksum exists) |
validate_patente |
License plate | pre-Mercosur and both Mercosur formats |
validate_cpa |
Postal code | legacy 4-digit and current CPA, province letter |
pip install ar-validators
Requires Python 3.9 or newer. Nothing else.
Every validator takes the number in any common written form — hyphens, dots and spaces are stripped before checking.
from ar_validators import (
validate_cuit, validate_cuil, validate_cbu,
validate_dni, validate_patente, validate_cpa,
)
validate_cuit("20000000001").valid # True
validate_cuit("20 00000000 1").valid # True
validate_cuil("30000000007").valid # False — company prefix
validate_cuil("30000000007").errors
# ["prefix '30' belongs to a legal entity; a CUIL is issued only to natural persons"]
cbu = validate_cbu("0075305826018159083016")
cbu.metadata["bank_code"] # "007"
cbu.metadata["branch_code"] # "5305"
validate_dni("12.345.678").value # "12345678"
validate_patente("AB123CD").metadata # format "mercosur", vehicle_type "auto"
validate_patente("A123BC").metadata # format "mercosur", vehicle_type "moto"
validate_patente("ABC123").metadata # format "pre_mercosur", vehicle_type None
validate_cpa("C1043AAZ").metadata["province"] # "Ciudad Autónoma de Buenos Aires"
validate_cpa("1043").metadata["format"] # "legacy"
from ar_validators import format_cuit, format_cbu, format_dni
format_cuit("20000000001") # "20-00000000-1"
format_cbu("0075305826018159083016")
# "00753058 26018159083016"
format_dni("12345678") # "12.345.678"
Formatters return None when the input has the wrong number of digits. They
do not check the checksum — validate first if that matters.
from ar_validators import cuit_type
cuit_type("20000000001") # "persona_fisica_masculino"
cuit_type("27000000006") # "persona_fisica_femenino"
cuit_type("30000000007") # "persona_juridica"
Every validator returns a frozen ValidationResult:
| Field | Type | Meaning |
|---|---|---|
valid |
bool |
True when every check passed |
value |
str |
The normalized input — digits only, or canonical uppercase for plates and postal codes |
errors |
list[str] |
Why it failed. Empty when valid |
metadata |
dict |
Information derived from a valid input. Empty when invalid |
The result is truthy when valid, so it reads naturally in a conditional:
if validate_cuit(user_input):
...
Putting a real CUIT in a test fixture means putting a real person's tax ID in your repository. Use the generators instead — they produce numbers with correct check digits that belong to nobody:
from ar_validators import generate_cuit, generate_cbu
generate_cuit() # random prefix
generate_cuit(prefix="27") # specific prefix
generate_cbu(bank_code="007") # specific bank
import random
generate_cuit(rng=random.Random(42)) # reproducible
Every number shown in this README is synthetic. The CUITs use an all-zero
base, which is never issued, and the CBU came out of generate_cbu. None of
them belongs to a real person or account.
This library validates format and checksums only. It does not verify whether a number is registered with any government agency. A CUIT that passes here is well-formed; whether it was ever issued, and to whom, is a question only ARCA can answer.
DNI numbers carry no check digit at all, so validate_dni can do no more than
confirm the shape and a plausible range.
MIT
Validación offline de números de identidad argentinos. Cero dependencias, sin llamadas de red.
from ar_validators import validate_cuit
result = validate_cuit("20-00000000-1")
result.valid # True
result.value # "20000000001"
result.metadata["type"] # "persona_fisica_masculino"
bad = validate_cuit("20-00000000-0")
bad.valid # False
bad.errors # ["invalid check digit: expected 1, got 0"]
Los números de identidad argentinos usan algoritmos de checksum fáciles de implementar mal de maneras sutiles. El dígito verificador del CUIT tiene dos casos especiales que la mayoría de los snippets se saltea. El CBU lleva dos dígitos verificadores independientes con tablas de multiplicadores distintas, y uno de esos multiplicadores es un 9 que se tipea como 1 lo bastante seguido como para que un validador roto igual pase una prueba superficial: solo difiere cuando un dígito puntual no es cero.
Esta librería los implementa bien, offline, sin ninguna llamada externa.
| Función | Entrada | Qué verifica |
|---|---|---|
validate_cuit |
CUIT / CUIL | 11 dígitos, prefijo conocido, dígito verificador módulo 11 |
validate_cuil |
CUIL | lo mismo, y rechaza prefijos de persona jurídica |
validate_cbu |
CBU | 22 dígitos, dos dígitos verificadores módulo 10 independientes |
validate_dni |
DNI | 7–8 dígitos, rango plausible (no existe checksum) |
validate_patente |
Patente | formato pre-Mercosur y ambos formatos Mercosur |
validate_cpa |
Código postal | 4 dígitos viejo y CPA actual, letra de provincia |
pip install ar-validators
Requiere Python 3.9 o superior. Nada más.
Cada validador acepta el número en cualquier forma escrita habitual: los guiones, puntos y espacios se sacan antes de verificar.
from ar_validators import (
validate_cuit, validate_cuil, validate_cbu,
validate_dni, validate_patente, validate_cpa,
)
validate_cuit("20000000001").valid # True
validate_cuit("20 00000000 1").valid # True
validate_cuil("30000000007").valid # False — prefijo de empresa
validate_cuil("30000000007").errors
# ["prefix '30' belongs to a legal entity; a CUIL is issued only to natural persons"]
cbu = validate_cbu("0075305826018159083016")
cbu.metadata["bank_code"] # "007"
cbu.metadata["branch_code"] # "5305"
validate_dni("12.345.678").value # "12345678"
validate_patente("AB123CD").metadata # format "mercosur", vehicle_type "auto"
validate_patente("A123BC").metadata # format "mercosur", vehicle_type "moto"
validate_patente("ABC123").metadata # format "pre_mercosur", vehicle_type None
validate_cpa("C1043AAZ").metadata["province"] # "Ciudad Autónoma de Buenos Aires"
validate_cpa("1043").metadata["format"] # "legacy"
from ar_validators import format_cuit, format_cbu, format_dni
format_cuit("20000000001") # "20-00000000-1"
format_cbu("0075305826018159083016")
# "00753058 26018159083016"
format_dni("12345678") # "12.345.678"
Los formateadores devuelven None cuando la entrada no tiene la cantidad de
dígitos correcta. No verifican el checksum: si eso importa, validá primero.
from ar_validators import cuit_type
cuit_type("20000000001") # "persona_fisica_masculino"
cuit_type("27000000006") # "persona_fisica_femenino"
cuit_type("30000000007") # "persona_juridica"
Cada validador devuelve un ValidationResult inmutable:
| Campo | Tipo | Significado |
|---|---|---|
valid |
bool |
True cuando pasó todas las verificaciones |
value |
str |
La entrada normalizada: solo dígitos, o mayúsculas canónicas para patentes y códigos postales |
errors |
list[str] |
Por qué falló. Vacío cuando es válido |
metadata |
dict |
Información derivada de una entrada válida. Vacío cuando es inválida |
El resultado es truthy cuando es válido, así que se lee natural en un condicional:
if validate_cuit(user_input):
...
Poner un CUIT real en un fixture de test es poner la clave fiscal de una persona real en tu repositorio. Usá los generadores: producen números con dígito verificador correcto que no son de nadie.
from ar_validators import generate_cuit, generate_cbu
generate_cuit() # prefijo al azar
generate_cuit(prefix="27") # prefijo específico
generate_cbu(bank_code="007") # banco específico
import random
generate_cuit(rng=random.Random(42)) # reproducible
Todos los números que aparecen en este README son sintéticos. Los CUIT usan
base cero, que nunca se emite, y el CBU salió de generate_cbu. Ninguno
pertenece a una persona ni a una cuenta real.
Esta librería valida formato y checksum, nada más. No verifica si un número está registrado en ningún organismo. Un CUIT que pasa acá está bien formado; si alguna vez fue emitido, y a nombre de quién, es una pregunta que solo ARCA puede contestar.
El DNI no tiene dígito verificador, así que validate_dni no puede hacer más
que confirmar la forma y un rango plausible.
MIT
Built by Carlos Perasso — OrvixLabs