rut-validator 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rut_validator/__init__.py +26 -0
- rut_validator/constants.py +3 -0
- rut_validator/core/__init__.py +14 -0
- rut_validator/core/formatter.py +32 -0
- rut_validator/core/orm/django/__init__.py +0 -0
- rut_validator/core/orm/django/schema.py +86 -0
- rut_validator/core/orm/pydantic/__init__.py +0 -0
- rut_validator/core/orm/pydantic/schema.py +80 -0
- rut_validator/core/orm/sqlalchemy/__init__.py +0 -0
- rut_validator/core/orm/sqlalchemy/schema.py +54 -0
- rut_validator/core/parser.py +59 -0
- rut_validator/core/patterns.py +123 -0
- rut_validator/core/validator.py +165 -0
- rut_validator/errors.py +25 -0
- rut_validator/py.typed +0 -0
- rut_validator/types/__init__.py +5 -0
- rut_validator/types/enums.py +18 -0
- rut_validator/types/rut.py +136 -0
- rut_validator/utils/__init__.py +0 -0
- rut_validator-0.1.0.dist-info/METADATA +223 -0
- rut_validator-0.1.0.dist-info/RECORD +24 -0
- rut_validator-0.1.0.dist-info/WHEEL +4 -0
- rut_validator-0.1.0.dist-info/entry_points.txt +3 -0
- rut_validator-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
rut-validator: Validation of Chilean RUTs for Pydantic and FastAPI
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .core.validator import RutValidator
|
|
6
|
+
from .core.patterns import RutFormat
|
|
7
|
+
from .core.orm.pydantic.schema import RutStr
|
|
8
|
+
from .errors import (
|
|
9
|
+
RutInvalidFormatError,
|
|
10
|
+
RutInvalidValueError,
|
|
11
|
+
RutModuleElevenValidationError,
|
|
12
|
+
RutValidationError,
|
|
13
|
+
)
|
|
14
|
+
from .types.rut import Rut as ValidatedRut
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
__all__ = [
|
|
18
|
+
"RutValidator", # For pure validation
|
|
19
|
+
"ValidatedRut", # Validation result (alias for Rut)
|
|
20
|
+
"RutStr", # For use with Pydantic
|
|
21
|
+
"RutFormat", # Format enumeration
|
|
22
|
+
"RutInvalidFormatError", # Custom exception
|
|
23
|
+
"RutInvalidValueError", # Custom exception
|
|
24
|
+
"RutModuleElevenValidationError", # Custom exception
|
|
25
|
+
"RutValidationError", # Base exception
|
|
26
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Core validation logic - no external dependencies"""
|
|
2
|
+
|
|
3
|
+
from .formatter import RutFormatter
|
|
4
|
+
from .parser import RutParser
|
|
5
|
+
from .patterns import RutPatterns, RutFormat
|
|
6
|
+
from .validator import RutValidator
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"RutFormatter",
|
|
10
|
+
"RutParser",
|
|
11
|
+
"RutPatterns",
|
|
12
|
+
"RutFormat",
|
|
13
|
+
"RutValidator",
|
|
14
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from rut_validator.core.patterns import RutPatterns
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RutFormatter:
|
|
5
|
+
"""Formatter for RUT strings with different output formats."""
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def to_original_format(rut: str) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Converts a RUT string to its original format with dots and dashes (e.g. "12345678-9" -> "12.345.678-9").
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
rut (str): The RUT string to format (e.g. "123456789")
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
str: The RUT string in original format (e.g. "12.345.678-9")
|
|
17
|
+
"""
|
|
18
|
+
return RutPatterns.formatted(rut)
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def to_normalize_format(rut: str) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Cleans the RUT string by removing dots and dashes, leaving only the digits and check digit.
|
|
24
|
+
This is used internally to normalize the RUT before validation.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
rut (str): The RUT string to clean (e.g. "12.345.678-9")
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: The cleaned RUT string (e.g. "123456789")
|
|
31
|
+
"""
|
|
32
|
+
return RutPatterns.normalized(rut)
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from django.db.models import CharField
|
|
2
|
+
from django.core.exceptions import ValidationError
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ...validator import RutValidator
|
|
7
|
+
from ....errors import (
|
|
8
|
+
RutInvalidValueError,
|
|
9
|
+
RutInvalidFormatError,
|
|
10
|
+
RutModuleElevenValidationError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RUTField(CharField):
|
|
15
|
+
description = "A field to store Chilean RUT (Rol Único Tributario) numbers."
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
18
|
+
kwargs.setdefault(
|
|
19
|
+
"max_length", 12
|
|
20
|
+
) # RUT can be up to 12 characters including formatting
|
|
21
|
+
super().__init__(*args, **kwargs)
|
|
22
|
+
|
|
23
|
+
# After initializing the field, we can set up any additional attributes or validators if needed.
|
|
24
|
+
# For example, we could add a validator that uses the RutValidator from the core module
|
|
25
|
+
|
|
26
|
+
def validator(value: Any) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Custom validator that uses the RutValidator to validate the RUT value.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
value (Any): The value to validate as a RUT.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ValidationError: If the value is not a valid RUT.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if value is None:
|
|
39
|
+
raise ValueError("RUT value cannot be None.")
|
|
40
|
+
|
|
41
|
+
RutValidator.validate(str(value))
|
|
42
|
+
|
|
43
|
+
except (
|
|
44
|
+
RutInvalidValueError,
|
|
45
|
+
RutInvalidFormatError,
|
|
46
|
+
RutModuleElevenValidationError,
|
|
47
|
+
) as e:
|
|
48
|
+
raise ValidationError(str(f"Invalid RUT value: {e}"))
|
|
49
|
+
|
|
50
|
+
self.validators.append(validator)
|
|
51
|
+
|
|
52
|
+
def to_python(self, value: object | None) -> str | None:
|
|
53
|
+
"""
|
|
54
|
+
Convert the input value to a Python string and validate it as a RUT.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
value (object | None): The value to convert and validate.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
str | None: The validated RUT string or None if the input was None.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
self.ensure_type(value)
|
|
64
|
+
|
|
65
|
+
if value is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
return RutValidator.validate(str(value)).normalized
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise ValidationError(str(e))
|
|
72
|
+
|
|
73
|
+
def get_prep_value(self, value: object | None) -> str | None:
|
|
74
|
+
return self.to_python(value)
|
|
75
|
+
|
|
76
|
+
def ensure_type(self, value: object | None) -> str | None:
|
|
77
|
+
|
|
78
|
+
# If the value is None, we can return it directly (or we could choose to return an empty string if we prefer).
|
|
79
|
+
if value is None:
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
# If the value is already a string, we can return it directly.
|
|
83
|
+
if not isinstance(value, str):
|
|
84
|
+
raise ValidationError("RUT value must be a string.")
|
|
85
|
+
|
|
86
|
+
return str(value)
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""RutStr - Pydantic-integrated RUT validator (style: EmailStr)"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Annotated
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
|
|
7
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
8
|
+
|
|
9
|
+
from pydantic_core.core_schema import str_schema, no_info_after_validator_function
|
|
10
|
+
from pydantic_core.core_schema import CoreSchema
|
|
11
|
+
|
|
12
|
+
from ...validator import RutValidator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
# For type checkers, we can just treat RutStr as a regular string.
|
|
17
|
+
RutStr = Annotated[str, ...]
|
|
18
|
+
|
|
19
|
+
else:
|
|
20
|
+
|
|
21
|
+
class RutStr(str):
|
|
22
|
+
@classmethod
|
|
23
|
+
def __get_pydantic_core_schema__(
|
|
24
|
+
cls,
|
|
25
|
+
_source_type: type[Any],
|
|
26
|
+
_handler: GetCoreSchemaHandler,
|
|
27
|
+
) -> CoreSchema:
|
|
28
|
+
"""
|
|
29
|
+
Defines the core schema for Pydantic validation.
|
|
30
|
+
This method is called by Pydantic to get the schema that will be used for validation
|
|
31
|
+
and parsing of the field.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
return no_info_after_validator_function(
|
|
35
|
+
cls._validate,
|
|
36
|
+
str_schema(),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def __get_pydantic_json_schema__(
|
|
41
|
+
cls,
|
|
42
|
+
schema: CoreSchema,
|
|
43
|
+
handler: GetJsonSchemaHandler,
|
|
44
|
+
) -> JsonSchemaValue:
|
|
45
|
+
"""Defines JSON schema for OpenAPI/Swagger"""
|
|
46
|
+
field_schema = handler(schema)
|
|
47
|
+
field_schema.update(
|
|
48
|
+
{
|
|
49
|
+
"type": "string",
|
|
50
|
+
"pattern": r"^\d{1,8}-[0-9kK]$|^\d{8}[0-9kK]$",
|
|
51
|
+
"example": "12345678-9",
|
|
52
|
+
"description": "Valid Chilean RUT (e.g: 12345678-9 or 123456789)",
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return field_schema
|
|
57
|
+
|
|
58
|
+
def __new__(cls, value: str) -> "RutStr":
|
|
59
|
+
validated = cls._validate(value)
|
|
60
|
+
return str.__new__(cls, validated)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def _validate(cls, input_value: str, /) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Validates the input value as a RUT and returns the normalized form.
|
|
66
|
+
If the input is not a valid RUT, it raises a ValueError with an appropriate message.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
input_value (str): The input value to validate as a RUT.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
str: The normalized RUT.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If the input is not a valid RUT.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
return RutValidator.validate(input_value).normalized
|
|
79
|
+
except Exception as e:
|
|
80
|
+
raise ValueError(str(e))
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from sqlalchemy.types import String, TypeDecorator
|
|
2
|
+
from sqlalchemy import Dialect
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from rut_validator.core.validator import RutValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RutSQLAlchemy(TypeDecorator):
|
|
9
|
+
"""
|
|
10
|
+
Custom SQLAlchemy type for RUT validation and normalization. This type will ensure that any value being stored in the database is a valid RUT and is normalized to a consistent format.
|
|
11
|
+
When retrieving values from the database, it returns the stored normalized string.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
impl = String # Save as VARCHAR/String into the database
|
|
15
|
+
cache_ok = True
|
|
16
|
+
|
|
17
|
+
def process_bind_param(self, value: str | None, dialect: Dialect) -> str | None:
|
|
18
|
+
"""
|
|
19
|
+
the `process_bind_param` method is called when a value is being bound to a parameter in a SQL statement. This is where we can validate and normalize the RUT value before it gets sent to the database.
|
|
20
|
+
If the value is None, we can return it directly (or we could choose to return an empty string if we prefer). If the value is already a RutStr instance, we can return its normalized form directly.
|
|
21
|
+
If it's a regular string, we validate and normalize it using the RutStr._validate method before returning it.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
value (Any): The value to be bound to the SQL parameter. This can be a string, a RutStr instance, or None.
|
|
25
|
+
dialect (Dialect): The SQLAlchemy dialect in use, which can be useful if we need to handle different database backends differently (though in this case we likely won't need it).
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str | None: The validated and normalized RUT string to be stored in the database, or None if the input was None.
|
|
29
|
+
"""
|
|
30
|
+
if value is None:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
validated_rut = RutValidator.validate(str(value))
|
|
35
|
+
return validated_rut.normalized
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise ValueError(str(e))
|
|
38
|
+
|
|
39
|
+
def process_result_value(self, value: str | None, dialect: Dialect) -> str | None:
|
|
40
|
+
"""
|
|
41
|
+
The `process_result_value` method is called when a value is being retrieved from the database.
|
|
42
|
+
This is where we can convert the stored string back into a RutStr object for easier manipulation in Python.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
value (Any): The value retrieved from the database. This can be a string or None.
|
|
46
|
+
dialect (Dialect): The SQLAlchemy dialect in use.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str | None: The stored normalized RUT string, or None if the input was None.
|
|
50
|
+
"""
|
|
51
|
+
if value is None:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
return value
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import Tuple, Optional
|
|
2
|
+
|
|
3
|
+
from rut_validator.core.patterns import RutPatterns
|
|
4
|
+
from rut_validator.types.enums import RutFormat
|
|
5
|
+
|
|
6
|
+
from rut_validator.errors import (
|
|
7
|
+
RutInvalidFormatError,
|
|
8
|
+
RutInvalidValueError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RutParser:
|
|
13
|
+
"""Parser for Chilean RUT strings with format detection."""
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def parse(cls, rut: str) -> Tuple[str, str, Optional[RutFormat]]:
|
|
17
|
+
"""
|
|
18
|
+
Parses a RUT string and returns body, check_digit, and detected format.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
rut: The RUT string to parse.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Tuple of (body, check_digit, format).
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
RutInvalidValueError: If RUT is empty or None.
|
|
28
|
+
RutInvalidFormatError: If RUT format is invalid.
|
|
29
|
+
"""
|
|
30
|
+
if rut.strip() == "":
|
|
31
|
+
raise RutInvalidValueError(
|
|
32
|
+
"No se puede parsear un RUT vacío, por favor ingrese un valor"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
body, check_digit, format_detected = cls.destructure(rut)
|
|
36
|
+
return body, check_digit, format_detected
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def destructure(cls, rut: str) -> Tuple[str, str, Optional[RutFormat]]:
|
|
40
|
+
"""
|
|
41
|
+
Destructures a RUT string into its body and check digit components,
|
|
42
|
+
while also detecting the input format.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
rut: The RUT string to parse.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Tuple of (body, check_digit, format).
|
|
49
|
+
"""
|
|
50
|
+
format_detected = RutPatterns.detect_format(rut)
|
|
51
|
+
|
|
52
|
+
if format_detected is None:
|
|
53
|
+
raise RutInvalidFormatError(
|
|
54
|
+
"Formato no válido, se esperaba algo como '12345678-9', '123456789' o '12.345.678-9'"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
normalized = RutPatterns.normalize(rut)
|
|
58
|
+
body, digit_check = normalized[:-1], normalized[-1]
|
|
59
|
+
return body, digit_check, format_detected
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""RUT pattern definitions and format detection."""
|
|
2
|
+
|
|
3
|
+
from re import compile
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from rut_validator.types.enums import RutFormat
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RutPatterns:
|
|
10
|
+
"""Collection of regex patterns for RUT validation and format detection."""
|
|
11
|
+
|
|
12
|
+
# Individual patterns for format detection
|
|
13
|
+
FORMATTED_PATTERN = compile(r"^\d{1,2}(?:\.\d{3}){2}-[\dkK]$")
|
|
14
|
+
HYPHENATED_PATTERN = compile(r"^\d{7,8}-[\dkK]$")
|
|
15
|
+
NORMALIZED_PATTERN = compile(r"^\d{7,8}[\dkK]$")
|
|
16
|
+
|
|
17
|
+
# Combined pattern for general validation
|
|
18
|
+
VALIDATION_PATTERN = compile(
|
|
19
|
+
r"^(?:\d{1,2}(?:\.\d{3}){2}-[\dkK]|\d{7,8}-[\dkK]|\d{7,8}[\dkK])$"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Maximum supported length for a formatted RUT string.
|
|
23
|
+
MAX_RUT_LENGTH = 15
|
|
24
|
+
|
|
25
|
+
# Cleaning pattern (removes dots, hyphens, keeps digits and K/k)
|
|
26
|
+
CLEANING_PATTERN = compile(r"[^0-9kK]")
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def detect_format(cls, rut: str) -> Optional[RutFormat]:
|
|
30
|
+
"""
|
|
31
|
+
Detect the format of a RUT string.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
rut: The RUT string to analyze.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
RutFormat if the format is recognized, None otherwise.
|
|
38
|
+
"""
|
|
39
|
+
if cls.FORMATTED_PATTERN.match(rut):
|
|
40
|
+
return RutFormat.FORMATTED
|
|
41
|
+
|
|
42
|
+
elif cls.HYPHENATED_PATTERN.match(rut):
|
|
43
|
+
return RutFormat.HYPHENATED
|
|
44
|
+
|
|
45
|
+
elif cls.NORMALIZED_PATTERN.match(rut):
|
|
46
|
+
return RutFormat.NORMALIZED
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def is_valid_format(cls, rut: str) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Check if a RUT string has a valid format (any supported format).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
rut: The RUT string to validate.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if the format is valid, False otherwise.
|
|
60
|
+
"""
|
|
61
|
+
if len(rut) > cls.MAX_RUT_LENGTH:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
return cls.VALIDATION_PATTERN.match(rut) is not None
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def normalize(cls, rut: str) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Normalize a RUT string by removing formatting characters.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
rut: The RUT string to normalize.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The normalized RUT string (digits + check digit, uppercased).
|
|
76
|
+
"""
|
|
77
|
+
return cls.CLEANING_PATTERN.sub("", rut).upper()
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def formatted(cls, rut: str) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Format a RUT string with dots and hyphen.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
rut: The RUT string to format (any supported RUT format).
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The formatted RUT string (e.g., "12.345.678-5").
|
|
89
|
+
"""
|
|
90
|
+
normalized_rut = cls.normalize(rut)
|
|
91
|
+
body = normalized_rut[:-1]
|
|
92
|
+
check_digit = normalized_rut[-1]
|
|
93
|
+
body_formatted = f"{int(body):,}".replace(",", ".")
|
|
94
|
+
return f"{body_formatted}-{check_digit}"
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def hyphenated(cls, rut: str) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Format a RUT string with hyphen only (hyphenated format).
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
rut: The RUT string to format (any supported RUT format).
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The hyphenated RUT string (e.g., "12345678-5").
|
|
106
|
+
"""
|
|
107
|
+
normalized_rut = cls.normalize(rut)
|
|
108
|
+
body = normalized_rut[:-1]
|
|
109
|
+
check_digit = normalized_rut[-1]
|
|
110
|
+
return f"{body}-{check_digit}"
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def normalized(cls, rut: str) -> str:
|
|
114
|
+
"""
|
|
115
|
+
Return the normalized RUT string without formatting.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
rut: The RUT string to normalize (any supported RUT format).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
The normalized RUT string (e.g., "123456785").
|
|
122
|
+
"""
|
|
123
|
+
return cls.normalize(rut)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Pure RUT validator - no dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rut_validator.core.parser import RutParser
|
|
10
|
+
from rut_validator.types.enums import ValidationResult
|
|
11
|
+
|
|
12
|
+
from rut_validator.errors import (
|
|
13
|
+
RutInvalidFormatError,
|
|
14
|
+
RutInvalidValueError,
|
|
15
|
+
RutModuleElevenValidationError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from rut_validator.constants import RUT_MODULE_ELEVEN_FACTORS
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from rut_validator.types.rut import Rut
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RutValidator:
|
|
27
|
+
"""Validator for Chilean RUTs with pure Python logic."""
|
|
28
|
+
|
|
29
|
+
__slots__ = []
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def get_validation_result(cls, rut: str) -> ValidationResult:
|
|
33
|
+
"""
|
|
34
|
+
Get the validation result for a RUT string.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
rut (str): The RUT string to validate.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
ValidationResult: The validation result.
|
|
41
|
+
"""
|
|
42
|
+
logger.debug(f"Getting validation result for RUT: {rut}")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
body, check_digit, _ = RutParser.parse(rut)
|
|
46
|
+
|
|
47
|
+
except RutInvalidValueError:
|
|
48
|
+
logger.debug("RUT invalid due to empty or missing value")
|
|
49
|
+
return ValidationResult.INVALID_VALUE
|
|
50
|
+
|
|
51
|
+
except RutInvalidFormatError:
|
|
52
|
+
logger.debug("RUT invalid due to incorrect format")
|
|
53
|
+
return ValidationResult.INVALID_FORMAT
|
|
54
|
+
|
|
55
|
+
if cls.is_valid_check_digit(body, check_digit):
|
|
56
|
+
logger.debug("RUT check digit is valid")
|
|
57
|
+
return ValidationResult.VALID
|
|
58
|
+
|
|
59
|
+
logger.debug("RUT check digit is invalid")
|
|
60
|
+
return ValidationResult.INVALID_CHECK_DIGIT
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def is_valid(cls, rut: str) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Check if a RUT string is valid without raising exceptions.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
rut (str): The RUT string to check.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
bool: True if valid, False otherwise.
|
|
72
|
+
"""
|
|
73
|
+
validation_result = cls.get_validation_result(rut)
|
|
74
|
+
is_valid = validation_result == ValidationResult.VALID
|
|
75
|
+
|
|
76
|
+
logger.debug(f"RUT {rut} validity: {is_valid} ({validation_result})")
|
|
77
|
+
|
|
78
|
+
return is_valid
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def validate(cls, rut: str) -> Rut:
|
|
82
|
+
"""
|
|
83
|
+
Validate a RUT string and return a Rut object.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
rut (str): The RUT string to validate.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Rut: A validated Rut object.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
RutInvalidValueError: If the RUT is empty or missing.
|
|
93
|
+
RutInvalidFormatError: If the RUT format is invalid.
|
|
94
|
+
RutModuleElevenValidationError: If the check digit does not match.
|
|
95
|
+
"""
|
|
96
|
+
logger.debug(f"Validating RUT: {rut}")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
body, check_digit, format_detected = RutParser.parse(rut)
|
|
100
|
+
|
|
101
|
+
except RutInvalidValueError:
|
|
102
|
+
raise RutInvalidValueError(
|
|
103
|
+
"No se puede parsear un RUT vacío, por favor ingrese un valor"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
except RutInvalidFormatError:
|
|
107
|
+
raise RutInvalidFormatError(
|
|
108
|
+
"Formato no válido, se esperaba algo como '12345678-9', "
|
|
109
|
+
"'123456789' o '12.345.678-9'"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if not cls.is_valid_check_digit(body, check_digit):
|
|
113
|
+
check_digit_expected = cls.module_eleven(body)
|
|
114
|
+
|
|
115
|
+
logger.warning(
|
|
116
|
+
f"Validation failed for RUT {rut}: expected {check_digit_expected}, got {check_digit}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
raise RutModuleElevenValidationError(
|
|
120
|
+
f"El dígito verificador no coincide, se esperaba '{check_digit_expected}' en vez de '{check_digit}'"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
from rut_validator.types.rut import Rut
|
|
124
|
+
|
|
125
|
+
logger.info(f"Validation successful for RUT: {rut}")
|
|
126
|
+
return Rut(rut, format_detected, skip_validation=True)
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def module_eleven(cls, body: str) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Calculates the check digit using the modulo 11 algorithm.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
str: The calculated check digit (0-9 or 'K')
|
|
135
|
+
"""
|
|
136
|
+
reversed_digits = map(int, reversed(body))
|
|
137
|
+
total = sum(
|
|
138
|
+
d * RUT_MODULE_ELEVEN_FACTORS[i % len(RUT_MODULE_ELEVEN_FACTORS)]
|
|
139
|
+
for i, d in enumerate(reversed_digits)
|
|
140
|
+
)
|
|
141
|
+
remainder = total % 11
|
|
142
|
+
result = 11 - remainder
|
|
143
|
+
|
|
144
|
+
if result == 11:
|
|
145
|
+
return "0"
|
|
146
|
+
|
|
147
|
+
if result == 10:
|
|
148
|
+
return "K"
|
|
149
|
+
|
|
150
|
+
return str(result)
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def is_valid_check_digit(cls, body: str, check_digit: str) -> bool:
|
|
154
|
+
"""
|
|
155
|
+
Validates that the provided check digit matches the calculated one.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
body (str): The numeric part of the RUT.
|
|
159
|
+
check_digit (str): The check digit to validate.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
bool: True if the check digit is valid, False otherwise.
|
|
163
|
+
"""
|
|
164
|
+
expected_check_digit = cls.module_eleven(body)
|
|
165
|
+
return check_digit.upper() == expected_check_digit
|
rut_validator/errors.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Custom exceptions for RUT validation."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RutValidationError(ValueError):
|
|
5
|
+
"""Base exception for all RUT validation errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RutInvalidValueError(RutValidationError):
|
|
11
|
+
"""Raised when the RUT value is empty or invalid."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RutInvalidFormatError(RutValidationError):
|
|
17
|
+
"""Raised when the RUT format is invalid."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RutModuleElevenValidationError(RutValidationError):
|
|
23
|
+
"""Raised when the RUT fails the modulo 11 validation."""
|
|
24
|
+
|
|
25
|
+
pass
|
rut_validator/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RutFormat(Enum):
|
|
5
|
+
"""Enumeration of supported RUT formats."""
|
|
6
|
+
|
|
7
|
+
FORMATTED = "formatted" # 12.345.678-9
|
|
8
|
+
HYPHENATED = "hyphenated" # 12345678-9
|
|
9
|
+
NORMALIZED = "normalized" # 123456789
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ValidationResult(Enum):
|
|
13
|
+
"""Result states for RUT validation."""
|
|
14
|
+
|
|
15
|
+
VALID = "valid"
|
|
16
|
+
INVALID_VALUE = "invalid_value"
|
|
17
|
+
INVALID_FORMAT = "invalid_format"
|
|
18
|
+
INVALID_CHECK_DIGIT = "invalid_check_digit"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Validated RUT value object."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from rut_validator.core.parser import RutParser
|
|
8
|
+
from rut_validator.core.patterns import RutPatterns, RutFormat
|
|
9
|
+
from rut_validator.errors import RutModuleElevenValidationError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(init=True)
|
|
13
|
+
class Rut:
|
|
14
|
+
"""
|
|
15
|
+
A class representing a validated RUT (Rol Único Tributario) value object.
|
|
16
|
+
|
|
17
|
+
This class encapsulates a validated RUT value and provides properties to access its components,
|
|
18
|
+
such as the body and check digit, as well as formatted and normalized representations.
|
|
19
|
+
|
|
20
|
+
The Rut class ensures that the RUT value is valid upon instantiation,
|
|
21
|
+
and it provides methods to retrieve the normalized and formatted versions of the RUT.
|
|
22
|
+
|
|
23
|
+
The normalized version is the raw RUT value without any formatting,
|
|
24
|
+
while the formatted version includes thousands separators and a hyphen before the check digit.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
value (str): The original RUT input string as provided by the caller.
|
|
28
|
+
format (Optional[RutFormat]): The detected format of the input.
|
|
29
|
+
|
|
30
|
+
Methods:
|
|
31
|
+
normalized: Returns the normalized RUT value.
|
|
32
|
+
formatted: Returns the formatted RUT value.
|
|
33
|
+
body: Returns the body of the RUT (the numeric part).
|
|
34
|
+
check_digit: Returns the check digit of the RUT.
|
|
35
|
+
is_formatted: Returns True if input was formatted format.
|
|
36
|
+
is_hyphenated: Returns True if input was hyphenated format.
|
|
37
|
+
is_normalized: Returns True if input was normalized format.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
__slots__ = ("value", "format")
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
value: str,
|
|
45
|
+
format_detected: Optional[RutFormat] = None,
|
|
46
|
+
*,
|
|
47
|
+
skip_validation: bool = False,
|
|
48
|
+
):
|
|
49
|
+
body, check_digit, detected_format = RutParser.destructure(value)
|
|
50
|
+
self.value = value
|
|
51
|
+
self.format = (
|
|
52
|
+
format_detected if format_detected is not None else detected_format
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if not skip_validation:
|
|
56
|
+
from rut_validator.core.validator import RutValidator
|
|
57
|
+
|
|
58
|
+
if not RutValidator.is_valid_check_digit(body, check_digit):
|
|
59
|
+
raise RutModuleElevenValidationError(
|
|
60
|
+
f"El dígito verificador no coincide, se esperaba '{RutValidator.module_eleven(body)}' en vez de '{check_digit}'"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def normalized(self) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Returns the normalized RUT value.
|
|
67
|
+
"""
|
|
68
|
+
return RutPatterns.normalize(self.value)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def formatted(self) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Returns the formatted RUT value.
|
|
74
|
+
"""
|
|
75
|
+
return RutPatterns.formatted(self.normalized)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def hyphenated(self) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Returns the hyphenated RUT value.
|
|
81
|
+
"""
|
|
82
|
+
return RutPatterns.hyphenated(self.normalized)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def body(self) -> int:
|
|
86
|
+
"""
|
|
87
|
+
Returns the body of the RUT (the numeric part).
|
|
88
|
+
"""
|
|
89
|
+
return int(self.normalized[:-1])
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def check_digit(self) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Returns the check digit of the RUT.
|
|
95
|
+
"""
|
|
96
|
+
return self.normalized[-1]
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def is_formatted(self) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Returns True if input was formatted format.
|
|
102
|
+
"""
|
|
103
|
+
return self.format == RutFormat.FORMATTED
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def is_hyphenated(self) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Returns True if input was hyphenated format.
|
|
109
|
+
"""
|
|
110
|
+
return self.format == RutFormat.HYPHENATED
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def is_normalized(self) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
Returns True if input was normalized format.
|
|
116
|
+
"""
|
|
117
|
+
return self.format == RutFormat.NORMALIZED
|
|
118
|
+
|
|
119
|
+
def __str__(self) -> str:
|
|
120
|
+
return self.formatted
|
|
121
|
+
|
|
122
|
+
def __repr__(self) -> str:
|
|
123
|
+
return f"Rut(value='{self.value}', format={self.format})"
|
|
124
|
+
|
|
125
|
+
def equals(self, other: object) -> bool:
|
|
126
|
+
"""Return True when two Rut objects represent the same normalized RUT."""
|
|
127
|
+
return self == other
|
|
128
|
+
|
|
129
|
+
def __eq__(self, other: object) -> bool:
|
|
130
|
+
if not isinstance(other, Rut):
|
|
131
|
+
return NotImplemented
|
|
132
|
+
|
|
133
|
+
return self.normalized == other.normalized
|
|
134
|
+
|
|
135
|
+
def __hash__(self) -> int:
|
|
136
|
+
return hash(self.normalized)
|
|
File without changes
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rut-validator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Chilean RUT validation library with Pydantic, Django, and SQLAlchemy support
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: rut,chile,validation,pydantic,django,sqlalchemy,fastapi
|
|
8
|
+
Author: Eli-ezer Reuven Ramirez Ruiz
|
|
9
|
+
Author-email: ramirez.ruiz.eliezer.reuven@gmail.com
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Provides-Extra: docs
|
|
25
|
+
Provides-Extra: fastapi
|
|
26
|
+
Provides-Extra: pydantic
|
|
27
|
+
Requires-Dist: black ; extra == "dev"
|
|
28
|
+
Requires-Dist: click (>=8.0.0,<9.0.0)
|
|
29
|
+
Requires-Dist: django (>=3.2,<5.0)
|
|
30
|
+
Requires-Dist: fastapi (>=0.111.0,<1.0.0) ; extra == "fastapi"
|
|
31
|
+
Requires-Dist: flake8 ; extra == "dev"
|
|
32
|
+
Requires-Dist: isort ; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy ; extra == "dev"
|
|
34
|
+
Requires-Dist: myst-parser ; extra == "docs"
|
|
35
|
+
Requires-Dist: pre-commit ; extra == "dev"
|
|
36
|
+
Requires-Dist: pydantic (>=2.12.5,<3.0.0) ; extra == "pydantic"
|
|
37
|
+
Requires-Dist: pytest (>=7.0.0) ; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov ; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff ; extra == "dev"
|
|
40
|
+
Requires-Dist: sphinx ; extra == "docs"
|
|
41
|
+
Requires-Dist: sphinx-rtd-theme ; extra == "docs"
|
|
42
|
+
Requires-Dist: sqlalchemy (>=2.0.49,<3.0.0)
|
|
43
|
+
Project-URL: Changelog, https://github.com/yourusername/rut-validator/blob/main/CHANGELOG.md
|
|
44
|
+
Project-URL: Documentation, https://rut-validator.readthedocs.io/
|
|
45
|
+
Project-URL: Homepage, https://github.com/yourusername/rut-validator
|
|
46
|
+
Project-URL: Issues, https://github.com/yourusername/rut-validator/issues
|
|
47
|
+
Project-URL: Repository, https://github.com/yourusername/rut-validator
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# rut-validator
|
|
51
|
+
|
|
52
|
+
[](https://pypi.org/project/rut-validator/)
|
|
53
|
+
[](https://pypi.org/project/rut-validator/)
|
|
54
|
+
[](https://opensource.org/licenses/MIT)
|
|
55
|
+
[](https://github.com/yourusername/rut-validator/actions)
|
|
56
|
+
[](https://codecov.io/gh/yourusername/rut-validator)
|
|
57
|
+
[](https://rut-validator.readthedocs.io/en/latest/?badge=latest)
|
|
58
|
+
|
|
59
|
+
Librería para validar RUT chileno con enfoque Pydantic-first y soporte completo para frameworks web.
|
|
60
|
+
|
|
61
|
+
## ✨ Características
|
|
62
|
+
|
|
63
|
+
- ✅ **Validación pura de RUT chileno** usando algoritmo módulo 11
|
|
64
|
+
- ✅ **Detección automática de formato**: dotted (`12.345.678-9`), hyphenated (`12345678-9`), numeric (`123456789`)
|
|
65
|
+
- ✅ **Integración completa con Pydantic** (`RutStr`)
|
|
66
|
+
- ✅ **Campo Django** (`RUTField`) listo para usar
|
|
67
|
+
- ✅ **Tipo SQLAlchemy** (`RutSQLAlchemy`) para bases de datos
|
|
68
|
+
- ✅ **Compatible con FastAPI** y otros frameworks web
|
|
69
|
+
- ✅ **Type hints completos** para mejor desarrollo
|
|
70
|
+
- ✅ **Sin dependencias externas** para funcionalidad core
|
|
71
|
+
- ✅ **Tests exhaustivos** con alta cobertura
|
|
72
|
+
|
|
73
|
+
## 🚀 Instalación
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install rut-validator
|
|
77
|
+
|
|
78
|
+
# Con soporte Pydantic
|
|
79
|
+
pip install rut-validator[pydantic]
|
|
80
|
+
|
|
81
|
+
# Con soporte FastAPI
|
|
82
|
+
pip install rut-validator[fastapi]
|
|
83
|
+
|
|
84
|
+
# Para desarrollo
|
|
85
|
+
pip install rut-validator[dev]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 📖 Uso Básico
|
|
89
|
+
|
|
90
|
+
### Validación Simple
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from rut_validator import RutValidator
|
|
94
|
+
|
|
95
|
+
# Validar RUT con cualquier formato
|
|
96
|
+
rut = RutValidator.validate("20.884.437-7")
|
|
97
|
+
print(f"RUT válido: {rut.formatted}") # "20.884.437-7"
|
|
98
|
+
print(f"Número: {rut.number}") # 20884437
|
|
99
|
+
print(f"Dígito: {rut.digit}") # "7"
|
|
100
|
+
print(f"Formato: {rut.format}") # RutFormat.DOTTED
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Detección de Formato
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from rut_validator import RutValidator
|
|
107
|
+
|
|
108
|
+
# La librería detecta automáticamente el formato de entrada
|
|
109
|
+
formats = [
|
|
110
|
+
"20.884.437-7", # Formato dotted
|
|
111
|
+
"20884437-7", # Formato hyphenated
|
|
112
|
+
"208844377", # Formato numeric
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
for rut_str in formats:
|
|
116
|
+
rut = RutValidator.validate(rut_str)
|
|
117
|
+
print(f"'{rut_str}' -> Formato: {rut.format}, Es dotted: {rut.is_dotted}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Con Pydantic
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from pydantic import BaseModel
|
|
124
|
+
from rut_validator import RutStr
|
|
125
|
+
|
|
126
|
+
class User(BaseModel):
|
|
127
|
+
name: str
|
|
128
|
+
rut: RutStr # Validación automática
|
|
129
|
+
|
|
130
|
+
# Uso
|
|
131
|
+
user = User(name="Juan Pérez", rut="12.345.678-5")
|
|
132
|
+
print(user.rut) # "12.345.678-5"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Con Django
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from django.db import models
|
|
139
|
+
from rut_validator.core.orm.django.schema import RUTField
|
|
140
|
+
|
|
141
|
+
class Person(models.Model):
|
|
142
|
+
name = models.CharField(max_length=100)
|
|
143
|
+
rut = RUTField(unique=True) # Validación automática en DB
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Con SQLAlchemy
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from sqlalchemy import Column, Integer, String
|
|
150
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
151
|
+
from rut_validator.core.orm.sqlalchemy.schema import RutSQLAlchemy
|
|
152
|
+
|
|
153
|
+
Base = declarative_base()
|
|
154
|
+
|
|
155
|
+
class Person(Base):
|
|
156
|
+
__tablename__ = 'persons'
|
|
157
|
+
|
|
158
|
+
id = Column(Integer, primary_key=True)
|
|
159
|
+
name = Column(String)
|
|
160
|
+
rut = Column(RutSQLAlchemy) # Validación automática
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 📚 Guía Completa
|
|
164
|
+
|
|
165
|
+
Lee la documentación completa en [docs/GUIA_RUT_VALIDATOR.md](docs/GUIA_RUT_VALIDATOR.md)
|
|
166
|
+
|
|
167
|
+
## 🧪 Ejemplos
|
|
168
|
+
|
|
169
|
+
- [Validación pura](examples/01_pure_validation.py)
|
|
170
|
+
- [Uso con Pydantic](examples/02_pydantic_usage.py)
|
|
171
|
+
- [Uso con FastAPI](examples/03_fastapi_usage.py)
|
|
172
|
+
|
|
173
|
+
## 🔧 Desarrollo
|
|
174
|
+
|
|
175
|
+
### Configuración del entorno
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Clonar repositorio
|
|
179
|
+
git clone https://github.com/yourusername/rut-validator.git
|
|
180
|
+
cd rut-validator
|
|
181
|
+
|
|
182
|
+
# Instalar dependencias de desarrollo
|
|
183
|
+
poetry install --with dev
|
|
184
|
+
|
|
185
|
+
# Ejecutar tests
|
|
186
|
+
poetry run pytest
|
|
187
|
+
|
|
188
|
+
# Ejecutar linting
|
|
189
|
+
poetry run black src/ tests/
|
|
190
|
+
poetry run isort src/ tests/
|
|
191
|
+
poetry run flake8 src/ tests/
|
|
192
|
+
poetry run mypy src/rut_validator/
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Pre-commit hooks
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
poetry run pre-commit install
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 🤝 Contribuir
|
|
202
|
+
|
|
203
|
+
1. Fork el proyecto
|
|
204
|
+
2. Crea una rama para tu feature (`git checkout -b feature/AmazingFeature`)
|
|
205
|
+
3. Commit tus cambios (`git commit -m 'Add some AmazingFeature'`)
|
|
206
|
+
4. Push a la rama (`git push origin feature/AmazingFeature`)
|
|
207
|
+
5. Abre un Pull Request
|
|
208
|
+
|
|
209
|
+
## 📄 Licencia
|
|
210
|
+
|
|
211
|
+
Este proyecto está bajo la Licencia MIT - ver el archivo [LICENSE](LICENSE) para más detalles.
|
|
212
|
+
|
|
213
|
+
## 🙏 Agradecimientos
|
|
214
|
+
|
|
215
|
+
- Algoritmo de validación basado en el estándar chileno del Servicio de Impuestos Internos
|
|
216
|
+
- Inspirado en bibliotecas similares pero con enfoque moderno y tipado fuerte
|
|
217
|
+
|
|
218
|
+
## 📞 Soporte
|
|
219
|
+
|
|
220
|
+
- 🐛 [Reportar bugs](https://github.com/yourusername/rut-validator/issues)
|
|
221
|
+
- 💡 [Sugerir features](https://github.com/yourusername/rut-validator/issues)
|
|
222
|
+
- 📖 [Documentación](https://rut-validator.readthedocs.io/)
|
|
223
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
rut_validator/__init__.py,sha256=UaZ-sj1ctf6KeVso3DtqXXudiHDDtoK5BhYeQ6VI_O8,793
|
|
2
|
+
rut_validator/constants.py,sha256=5VXbvM2vHhRGwz_E6bvNuexZTugsKh82DCdrVbB4R5o,84
|
|
3
|
+
rut_validator/core/__init__.py,sha256=S8IGTUcn-COEUEyOiBvQsBt2AzFxBdKwPcVYKyOcB5k,311
|
|
4
|
+
rut_validator/core/formatter.py,sha256=gdwohjrJVYGUYuS-yftnkjj08fKLMZYrUSsExQbcQdE,1022
|
|
5
|
+
rut_validator/core/orm/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
rut_validator/core/orm/django/schema.py,sha256=FaKdER1GW7EVB582zp4kpiRiAjJ5Oiz-4JsFh0ns-hc,2703
|
|
7
|
+
rut_validator/core/orm/pydantic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
rut_validator/core/orm/pydantic/schema.py,sha256=jG4kCbF2bgqbnZFai-xp_BSR-dyNuwJrY4YPbHCzKEw,2589
|
|
9
|
+
rut_validator/core/orm/sqlalchemy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
rut_validator/core/orm/sqlalchemy/schema.py,sha256=YBiO1wBAJUe1MIM0_KUoVEFDFj-APrboH7j8EYbVWBE,2576
|
|
11
|
+
rut_validator/core/parser.py,sha256=HVyn9aI_SaHbAaSJKw30crE0ot-WJX7o2v4dvoSUwg8,1822
|
|
12
|
+
rut_validator/core/patterns.py,sha256=JcDhtQ1FlBLW8sWADRUnG-Gc8YFUeMwRzIsMXkZ5fhs,3538
|
|
13
|
+
rut_validator/core/validator.py,sha256=-jeA86GbLlglfllYSbXh1_A6cCXdNf_NQOcDT7h6uW8,4915
|
|
14
|
+
rut_validator/errors.py,sha256=X_iM1yqssh_-2QAgC0IOud5bGavA_1J6LEFeM5flFss,509
|
|
15
|
+
rut_validator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
rut_validator/types/__init__.py,sha256=2t2lpWbiSKLE4P6g8_FCyNYAXBtAw5VMRJNX3QxGOnI,114
|
|
17
|
+
rut_validator/types/enums.py,sha256=72wIRUQN-xcKT1Ix0tOpIafKvW1HE9xr1HYZTiO5xLc,446
|
|
18
|
+
rut_validator/types/rut.py,sha256=7LAYFF3XAbxaVa9XpsnLevJDQXZBEuyCw7hA7dbXO6c,4241
|
|
19
|
+
rut_validator/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
rut_validator-0.1.0.dist-info/METADATA,sha256=u7Ovic-hleqo5J7dMHnxfrhgwyGClR8ZfzE2EjXOZp8,7210
|
|
21
|
+
rut_validator-0.1.0.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
22
|
+
rut_validator-0.1.0.dist-info/entry_points.txt,sha256=UBDN0ZY58VIptPbuUnPIpnJubUOKFcbvXofDgjdUMs0,55
|
|
23
|
+
rut_validator-0.1.0.dist-info/licenses/LICENSE,sha256=RWPNeNp8UvOe5ReVomHctEU8umwticGydzEwaj4Ihag,1084
|
|
24
|
+
rut_validator-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eli-ezer Reuven Ramirez Ruiz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|