Skip to content

Commit

Permalink
Merge pull request #237 from fyntex/release/v0.14.0
Browse files Browse the repository at this point in the history
Release v0.14.0
  • Loading branch information
ycouce-cdd committed Aug 17, 2021
2 parents 8e390e0 + fd15611 commit 79b9e85
Show file tree
Hide file tree
Showing 5 changed files with 492 additions and 147 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.13.0
current_version = 0.14.0
commit = True
tag = True

Expand Down
7 changes: 6 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
History
-------

0.14.0 (2021-08-17)
+++++++++++++++++++++++

* (PR #234, 2021-08-09) dte: Convert stdlib dataclasses into pydantic dataclasses
* (PR #236, 2021-08-09) DteXmlData: Restore mistakenly deleted regression tests

0.13.0 (2021-07-14)
+++++++++++++++++++++++

* (PR #228, 2021-06-29) config: Update Python version used in CI jobs to 3.8.10
* (PR #230, 2021-07-09) dte: Convert stdlib dataclass `DteXmlData` into pydantic dataclass


0.12.5 (2021-06-15)
+++++++++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion cl_sii/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
"""


__version__ = '0.13.0'
__version__ = '0.14.0'
215 changes: 96 additions & 119 deletions cl_sii/dte/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"""
import dataclasses
from dataclasses import field as dc_field
from datetime import date, datetime
from typing import Mapping, Optional

Expand Down Expand Up @@ -99,7 +98,12 @@ def validate_non_empty_bytes(value: bytes) -> None:
raise ValueError("Bytes value length is 0.")


@dataclasses.dataclass(frozen=True)
@pydantic.dataclasses.dataclass(
frozen=True,
config=type('Config', (), dict(
arbitrary_types_allowed=True,
))
)
class DteNaturalKey:

"""
Expand All @@ -121,40 +125,21 @@ class DteNaturalKey:
"""

emisor_rut: Rut = dc_field()
emisor_rut: Rut
"""
RUT of the "emisor" of the DTE.
"""

tipo_dte: TipoDteEnum = dc_field()
tipo_dte: TipoDteEnum
"""
The kind of DTE.
"""

folio: int = dc_field()
folio: int
"""
The sequential number of a DTE of given kind issued by 'emisor_rut'.
"""

def __post_init__(self) -> None:
"""
Run validation automatically after setting the fields values.
:raises TypeError, ValueError:
"""

if not isinstance(self.emisor_rut, Rut):
raise TypeError("Inappropriate type of 'emisor_rut'.")

if not isinstance(self.tipo_dte, TipoDteEnum):
raise TypeError("Inappropriate type of 'tipo_dte'.")

if not isinstance(self.folio, int):
raise TypeError("Inappropriate type of 'folio'.")

validate_dte_folio(self.folio)

def as_dict(self) -> Mapping[str, object]:
return dataclasses.asdict(self)

Expand All @@ -172,8 +157,23 @@ def slug(self) -> str:

return f'{self.emisor_rut}--{self.tipo_dte}--{self.folio}'

###########################################################################
# Validators
###########################################################################

@pydantic.validator('folio')
def validate_folio(cls, v: object) -> object:
if isinstance(v, int):
validate_dte_folio(v)
return v

@dataclasses.dataclass(frozen=True)

@pydantic.dataclasses.dataclass(
frozen=True,
config=type('Config', (), dict(
arbitrary_types_allowed=True,
))
)
class DteDataL0(DteNaturalKey):

"""
Expand Down Expand Up @@ -206,7 +206,12 @@ def natural_key(self) -> DteNaturalKey:
return DteNaturalKey(emisor_rut=self.emisor_rut, tipo_dte=self.tipo_dte, folio=self.folio)


@dataclasses.dataclass(frozen=True)
@pydantic.dataclasses.dataclass(
frozen=True,
config=type('Config', (), dict(
arbitrary_types_allowed=True,
))
)
class DteDataL1(DteDataL0):

"""
Expand All @@ -232,7 +237,7 @@ class DteDataL1(DteDataL0):
"""

fecha_emision_date: date = dc_field()
fecha_emision_date: date
"""
Field 'fecha_emision' of the DTE.
Expand All @@ -241,36 +246,16 @@ class DteDataL1(DteDataL0):
"""

receptor_rut: Rut = dc_field()
receptor_rut: Rut
"""
RUT of the "receptor" of the DTE.
"""

monto_total: int = dc_field()
monto_total: int
"""
Total amount of the DTE.
"""

def __post_init__(self) -> None:
"""
Run validation automatically after setting the fields values.
:raises TypeError, ValueError:
"""
super().__post_init__()

if not isinstance(self.fecha_emision_date, date):
raise TypeError("Inappropriate type of 'fecha_emision_date'.")

if not isinstance(self.receptor_rut, Rut):
raise TypeError("Inappropriate type of 'receptor_rut'.")

if not isinstance(self.monto_total, int):
raise TypeError("Inappropriate type of 'monto_total'.")

validate_dte_monto_total(self.monto_total, self.tipo_dte)

###########################################################################
# properties
###########################################################################
Expand Down Expand Up @@ -319,8 +304,26 @@ def deudor_rut(self) -> Rut:
"""
return self.comprador_rut

###########################################################################
# Validators
###########################################################################

@pydantic.validator('monto_total')
def validate_monto_total(cls, v: object, values: Mapping[str, object]) -> object:
tipo_dte = values.get('tipo_dte')

if isinstance(v, int) and isinstance(tipo_dte, TipoDteEnum):
validate_dte_monto_total(v, tipo_dte=tipo_dte)

return v


@dataclasses.dataclass(frozen=True)
@pydantic.dataclasses.dataclass(
frozen=True,
config=type('Config', (), dict(
arbitrary_types_allowed=True,
))
)
class DteDataL2(DteDataL1):

"""
Expand Down Expand Up @@ -349,32 +352,32 @@ class DteDataL2(DteDataL1):
# fields
###########################################################################

emisor_razon_social: Optional[str] = dc_field()
emisor_razon_social: Optional[str]
"""
"Razón social" (legal name) of the "emisor" of the DTE.
"""

receptor_razon_social: Optional[str] = dc_field()
receptor_razon_social: Optional[str]
"""
"Razón social" (legal name) of the "receptor" of the DTE.
"""

fecha_vencimiento_date: Optional[date] = dc_field(default=None)
fecha_vencimiento_date: Optional[date] = None
"""
"Fecha de vencimiento (pago)" of the DTE.
"""

firma_documento_dt: Optional[datetime] = dc_field(default=None)
firma_documento_dt: Optional[datetime] = None
"""
Datetime on which the "documento" was digitally signed.
"""

signature_value: Optional[bytes] = dc_field(default=None)
signature_value: Optional[bytes] = None
"""
DTE's digital signature's value (raw bytes, without base64 encoding).
"""

signature_x509_cert_der: Optional[bytes] = dc_field(default=None)
signature_x509_cert_der: Optional[bytes] = None
"""
DTE's digital signature's DER-encoded X.509 cert.
Expand All @@ -383,81 +386,21 @@ class DteDataL2(DteDataL1):
and :func:`cl_sii.libs.crypto_utils.x509_cert_der_to_pem`.
"""

emisor_giro: Optional[str] = dc_field(default=None)
emisor_giro: Optional[str] = None
"""
"Giro" of the "emisor" of the DTE.
"""

emisor_email: Optional[str] = dc_field(default=None)
emisor_email: Optional[str] = None
"""
Email address of the "emisor" of the DTE.
"""

receptor_email: Optional[str] = dc_field(default=None)
receptor_email: Optional[str] = None
"""
Email address of the "receptor" of the DTE.
"""

def __post_init__(self) -> None:
"""
Run validation automatically after setting the fields values.
:raises TypeError, ValueError:
"""
super().__post_init__()

if self.emisor_razon_social is not None:
if not isinstance(self.emisor_razon_social, str):
raise TypeError("Inappropriate type of 'emisor_razon_social'.")
validate_contribuyente_razon_social(self.emisor_razon_social)

if self.receptor_razon_social is not None:
if not isinstance(self.receptor_razon_social, str):
raise TypeError("Inappropriate type of 'receptor_razon_social'.")
validate_contribuyente_razon_social(self.receptor_razon_social)

if self.fecha_vencimiento_date is not None:
if not isinstance(self.fecha_vencimiento_date, date):
raise TypeError("Inappropriate type of 'fecha_vencimiento_date'.")

if self.firma_documento_dt is not None:
if not isinstance(self.firma_documento_dt, datetime):
raise TypeError("Inappropriate type of 'firma_documento_dt'.")
tz_utils.validate_dt_tz(self.firma_documento_dt, self.DATETIME_FIELDS_TZ)

if self.signature_value is not None:
if not isinstance(self.signature_value, bytes):
raise TypeError("Inappropriate type of 'signature_value'.")
# warning: do NOT strip a bytes value because "strip" implies an ASCII-encoded text,
# which in this case it is not.
validate_non_empty_bytes(self.signature_value)

if self.signature_x509_cert_der is not None:
if not isinstance(self.signature_x509_cert_der, bytes):
raise TypeError("Inappropriate type of 'signature_x509_cert_der'.")
# warning: do NOT strip a bytes value because "strip" implies an ASCII-encoded text,
# which in this case it is not.
validate_non_empty_bytes(self.signature_x509_cert_der)

if self.emisor_giro is not None:
if not isinstance(self.emisor_giro, str):
raise TypeError("Inappropriate type of 'emisor_giro'.")
validate_clean_str(self.emisor_giro)
validate_non_empty_str(self.emisor_giro)

if self.emisor_email is not None:
if not isinstance(self.emisor_email, str):
raise TypeError("Inappropriate type of 'emisor_email'.")
validate_clean_str(self.emisor_email)
validate_non_empty_str(self.emisor_email)

if self.receptor_email is not None:
if not isinstance(self.receptor_email, str):
raise TypeError("Inappropriate type of 'receptor_email'.")
validate_clean_str(self.receptor_email)
validate_non_empty_str(self.receptor_email)

def as_dte_data_l1(self) -> DteDataL1:
return DteDataL1(
emisor_rut=self.emisor_rut,
Expand All @@ -467,6 +410,40 @@ def as_dte_data_l1(self) -> DteDataL1:
receptor_rut=self.receptor_rut,
monto_total=self.monto_total)

###########################################################################
# Validators
###########################################################################

@pydantic.validator('emisor_razon_social', 'receptor_razon_social')
def validate_contribuyente_razon_social(cls, v: object) -> object:
if isinstance(v, str):
validate_contribuyente_razon_social(v)
return v

@pydantic.validator('firma_documento_dt')
def validate_datetime_tz(cls, v: object) -> object:
if isinstance(v, datetime):
tz_utils.validate_dt_tz(v, cls.DATETIME_FIELDS_TZ)
return v

@pydantic.validator('signature_value', 'signature_x509_cert_der')
def validate_non_empty_bytes(cls, v: object) -> object:
if isinstance(v, bytes):
validate_non_empty_bytes(v)
return v

@pydantic.validator('emisor_giro', 'emisor_email', 'receptor_email')
def validate_no_leading_or_trailing_whitespace_characters(cls, v: object) -> object:
if isinstance(v, str):
validate_clean_str(v)
return v

@pydantic.validator('emisor_giro', 'emisor_email', 'receptor_email')
def validate_non_empty_stripped_str(cls, v: object) -> object:
if isinstance(v, str):
validate_non_empty_str(v)
return v


@pydantic.dataclasses.dataclass(
frozen=True,
Expand Down

0 comments on commit 79b9e85

Please sign in to comment.