Skip to content

A Pydantic custom type for Chilean RUT (Rol Único Tributario) validation and formatting.

License

flolas/pydantic-type-chile-rut

Repository files navigation

pydantic-type-chile-rut

CI PyPI version PyPI Downloads Python versions License: MIT

A Pydantic custom type for Chilean RUT (Rol Único Tributario) validation and formatting.

Features

  • Comprehensive validation: Validates RUT format, length, and check digit
  • 🔧 Flexible input: Accepts RUTs with or without dots and hyphens
  • 📝 Multiple formats: Provides both compact and formatted output
  • 🏷️ Type-safe: Full Pydantic v2 integration with proper type hints
  • 🧪 Well-tested: Comprehensive test suite with 100% coverage
  • 🚀 High performance: Efficient validation using Pydantic's core schema

Installation

pip install pydantic-type-chile-rut

Or using uv:

uv add pydantic-type-chile-rut

Quick Start

from pydantic import BaseModel
from pydantic_type_chile_rut import RutNumber

class Person(BaseModel):
    name: str
    rut: RutNumber

# Create a person with a valid RUT
person = Person(name="Juan Pérez", rut="12.345.678-5")

print(person.rut)           # "12345678-5" (compact format)
print(person.rut.formatted) # "12.345.678-5" (with dots)
print(person.rut.number)    # 12345678 (numeric part)
print(person.rut.dv)        # "5" (check digit)

RUT Format Support

The library accepts RUTs in various formats:

from pydantic_type_chile_rut import RutNumber

# All these inputs are equivalent and valid:
valid_formats = [
    "12.345.678-5",    # Standard format with dots and hyphen
    "12345678-5",      # Without dots
    "123456785",       # Without dots or hyphen
    "12.345.678-5",    # With spaces (trimmed automatically)
]

# Case insensitive check digits:
rut_with_k = RutNumber.model_validate("15.345.678-k")  # -> "15345678-K"

RutNumber Properties

The RutNumber class provides several useful properties and methods:

from pydantic_type_chile_rut import RutNumber

rut = RutNumber(12345678, "5")

# Properties
print(rut.number)     # 12345678 (int)
print(rut.dv)         # "5" (str)
print(str(rut))       # "12345678-5" (compact format)

# Formatting methods
print(rut.formatted)   # "12.345.678-5" (property)
print(rut.with_dots()) # "12.345.678-5" (method)

# Representation
print(repr(rut))      # "RutNumber(number=12345678, dv='5')"

Validation Examples

Valid RUTs

from pydantic import BaseModel
from pydantic_type_chile_rut import RutNumber

class TestModel(BaseModel):
    rut: RutNumber

# These will all validate successfully:
valid_ruts = [
    "12.345.678-5",      # Standard format
    "15.345.678-K",      # With K check digit
    "1-9",               # Short RUT
    "0-0",               # Zero RUT (special case)
    "123456785",         # Without punctuation
    "00001234-3",        # With leading zeros (normalized)
]

for rut_str in valid_ruts:
    model = TestModel(rut=rut_str)
    print(f"{rut_str} -> {model.rut}")

Invalid RUTs

from pydantic import ValidationError

invalid_ruts = [
    "12345678-4",        # Wrong check digit
    "ABC",               # Not numeric
    "12.345.678-",       # Missing check digit
    "",                  # Empty string
    "123.456.789-0",     # Too long (> 9 digits)
    "12345678-X",        # Invalid check digit character
]

for rut_str in invalid_ruts:
    try:
        TestModel(rut=rut_str)
    except ValidationError as e:
        print(f"{rut_str} -> {e.errors()[0]['msg']}")

Integration with Pydantic Models

Basic Usage

from pydantic import BaseModel
from typing import Optional
from pydantic_type_chile_rut import RutNumber

class Employee(BaseModel):
    id: int
    name: str
    rut: RutNumber
    supervisor_rut: Optional[RutNumber] = None

employee = Employee(
    id=1,
    name="María González",
    rut="18.765.432-1",
    supervisor_rut="12.345.678-5"
)

# Serialization
employee_dict = employee.model_dump()
print(employee_dict)
# {
#     'id': 1,
#     'name': 'María González',
#     'rut': '18765432-1',
#     'supervisor_rut': '12345678-5'
# }

JSON Schema Generation

from pydantic import BaseModel
from pydantic_type_chile_rut import RutNumber

class Person(BaseModel):
    rut: RutNumber

# Generate JSON schema
schema = Person.model_json_schema()
print(schema)

Using with TypeAdapter

from pydantic import TypeAdapter
from pydantic_type_chile_rut import RutNumber

adapter = TypeAdapter(RutNumber)

# Validate single RUT
rut = adapter.validate_python("12.345.678-5")
print(f"Valid RUT: {rut.formatted}")

# Validate list of RUTs
ruts_adapter = TypeAdapter(list[RutNumber])
ruts = ruts_adapter.validate_python([
    "12.345.678-5",
    "15.345.678-K",
    "1-9"
])

Advanced Usage

Custom Validation

from pydantic import BaseModel, field_validator
from pydantic_type_chile_rut import RutNumber

class BusinessEntity(BaseModel):
    business_rut: RutNumber
    legal_rep_rut: RutNumber

    @field_validator('business_rut')
    @classmethod
    def validate_business_rut(cls, v):
        # Business RUTs in Chile typically have 8-9 digits
        if v.number < 50000000:
            raise ValueError('Business RUT seems too low')
        return v

entity = BusinessEntity(
    business_rut="96.511.760-1",
    legal_rep_rut="12.345.678-5"
)

Working with Databases

from pydantic import BaseModel
from sqlalchemy import Column, String, Integer
from pydantic_type_chile_rut import RutNumber

# SQLAlchemy model
class PersonDB:
    __tablename__ = 'persons'

    id = Column(Integer, primary_key=True)
    rut = Column(String(12))  # Store as "12345678-5"
    name = Column(String(100))

# Pydantic model for API
class PersonAPI(BaseModel):
    id: int
    rut: RutNumber
    name: str

    @classmethod
    def from_db(cls, db_person):
        return cls(
            id=db_person.id,
            rut=db_person.rut,  # Automatically validates
            name=db_person.name
        )

Check Digit Algorithm

The library implements the standard Chilean RUT check digit algorithm (modulo 11):

  1. Multiply each digit by weights [2, 3, 4, 5, 6, 7] from right to left, cycling
  2. Sum all products
  3. Calculate 11 - (sum % 11)
  4. If result is 11, use "0"; if 10, use "K"; otherwise use the number

Development

This project uses uv for dependency management and packaging.

Setup Development Environment

# Clone the repository
git clone https://github.com/flolas/pydantic-type-chile-rut.git
cd pydantic-type-chile-rut

# Install dependencies
uv sync --dev

# Install pre-commit hooks
uv run pre-commit install

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=src/pydantic_type_chile_rut --cov-report=html

# Run linting and formatting
uv run pre-commit run --all-files

Project Structure

pydantic-type-chile-rut/
├── src/
│   └── pydantic_type_chile_rut/
│       ├── __init__.py
│       └── rut.py
├── tests/
│   ├── __init__.py
│   └── test_rut.py
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── pyproject.toml
├── README.md
└── LICENSE

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Guidelines

  • Add tests for any new features
  • Ensure all tests pass
  • Follow the existing code style
  • Update documentation as needed

Testing

Run the full test suite:

uv run pytest tests/ -v

Run with coverage:

uv run pytest tests/ --cov=src/pydantic_type_chile_rut --cov-report=term-missing

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

See CHANGELOG.md for a history of changes.

Support

If you encounter any issues or have questions, please open an issue on GitHub.

Related Projects

Acknowledgments

  • Thanks to the Pydantic team for creating an excellent validation library
  • Inspired by various Chilean RUT validation implementations

About

A Pydantic custom type for Chilean RUT (Rol Único Tributario) validation and formatting.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages