Skip to content

Commit

Permalink
data.ref: versioning XML schemas of "factura electrónica"
Browse files Browse the repository at this point in the history
Move the content of the `schemas-xml` directory into a subdirectory
named `2011_05_30`. This name represents the version of these XML
schemas according to the last update timestamp of the files in the set.

Extra:
New enum `cl_sii.dte.constants.XmlSchemasVersionEnum` to define
the available XML schema versions
  • Loading branch information
ycouce-cdd committed Apr 12, 2021
1 parent 6d84a11 commit acc4ce0
Show file tree
Hide file tree
Showing 18 changed files with 96 additions and 17 deletions.
14 changes: 14 additions & 0 deletions cl_sii/dte/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,17 @@ def emisor_is_vendedor(self) -> bool:
@property
def receptor_is_vendedor(self) -> bool:
return self.is_factura_compra


class XmlSchemasVersionEnum(enum.Enum):
"""
Enum of "SII XML Schema Versions".
The version name is selected considering the last updated timestamp
of the files in the set
"""

LATEST = '2011_05_30'
"""Reference to the latest version available"""

V2011_05_30 = '2011_05_30'
33 changes: 29 additions & 4 deletions cl_sii/dte/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import logging
import os
from datetime import date, datetime
from lxml.etree import XMLSchema as XmlSchema
from typing import Optional, Tuple

from cl_sii.libs import encoding_utils
Expand Down Expand Up @@ -52,11 +53,13 @@
Mapping from XML namespace prefix to full name, for DTE processing.
"""


_DTE_XML_SCHEMA_STR_PATH_TEMPLATE = (
"data/ref/factura_electronica/schemas-xml/{}/EnvioDTE_v10.xsd"
)
_DTE_XML_SCHEMA_PATH = os.path.abspath(
os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'data/ref/factura_electronica/schemas-xml/EnvioDTE_v10.xsd',
_DTE_XML_SCHEMA_STR_PATH_TEMPLATE.format(constants.XmlSchemasVersionEnum.LATEST.value),
)
)
DTE_XML_SCHEMA_OBJ = xml_utils.read_xml_schema(_DTE_XML_SCHEMA_PATH)
Expand Down Expand Up @@ -103,15 +106,20 @@ def clean_dte_xml(
return xml_doc, modified


def validate_dte_xml(xml_doc: XmlElement) -> None:
def validate_dte_xml(
xml_doc: XmlElement,
dte_xml_schema_version: constants.XmlSchemasVersionEnum = constants.XmlSchemasVersionEnum.LATEST
) -> None:
"""
Validate ``xml_doc`` against DTE's XML schema.
:raises xml_utils.XmlSchemaDocValidationError:
"""

dte_xml_schema_obj = _get_dte_xml_schema_obj(dte_xml_schema_version)
# TODO: add better and more precise exception handling.
xml_utils.validate_xml_doc(DTE_XML_SCHEMA_OBJ, xml_doc)
xml_utils.validate_xml_doc(dte_xml_schema_obj, xml_doc, dte_xml_schema_version.value)


def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData:
Expand Down Expand Up @@ -512,6 +520,23 @@ def _text_strip_or_raise(xml_em: XmlElement) -> str:
return stripped_text


def _get_dte_xml_schema_obj(dte_xml_schema_version: constants.XmlSchemasVersionEnum) -> XmlSchema:

if(dte_xml_schema_version is constants.XmlSchemasVersionEnum.LATEST):
dte_xml_schema_obj = DTE_XML_SCHEMA_OBJ
else:
dte_xml_schema_path = os.path.abspath(
os.path.join(
os.path.dirname(os.path.dirname(__file__)),
_DTE_XML_SCHEMA_STR_PATH_TEMPLATE.format(dte_xml_schema_version.value),
)
)

dte_xml_schema_obj = xml_utils.read_xml_schema(dte_xml_schema_path)

return dte_xml_schema_obj


###############################################################################
# helpers
###############################################################################
Expand Down
11 changes: 9 additions & 2 deletions cl_sii/libs/xml_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import io
import logging
import os
from typing import IO, Tuple, Union
from typing import IO, Optional, Tuple, Union

import defusedxml
import defusedxml.lxml
Expand Down Expand Up @@ -275,7 +275,11 @@ def read_xml_schema(filename: str) -> XmlSchema:
raise ValueError("XML schema file not found.", filename)


def validate_xml_doc(xml_schema: XmlSchema, xml_doc: XmlElement) -> None:
def validate_xml_doc(
xml_schema: XmlSchema,
xml_doc: XmlElement,
xml_schema_version: Optional[str] = None,
) -> None:
"""
Validate ``xml_doc`` against XML schema ``xml_schema``.
Expand Down Expand Up @@ -308,6 +312,9 @@ def validate_xml_doc(xml_schema: XmlSchema, xml_doc: XmlElement) -> None:
# "Element 'DTE': No matching global declaration available for the validation root., line 2" # noqa: E501
validation_error_msg = str(exc)

if (xml_schema_version and xml_schema_version.strip()):
validation_error_msg += f". XML schemas version {xml_schema_version}"

raise XmlSchemaDocValidationError(validation_error_msg) from exc


Expand Down
37 changes: 32 additions & 5 deletions cl_sii/rtc/parse_aec.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

import logging
from datetime import date, datetime
from lxml.etree import XMLSchema as XmlSchema
from pathlib import Path
from typing import Mapping, Optional, Sequence

import pydantic

import cl_sii.dte.data_models
import cl_sii.dte.parse
from cl_sii.dte.constants import TipoDteEnum
from cl_sii.dte.constants import TipoDteEnum, XmlSchemasVersionEnum
from cl_sii.dte.data_models import DteXmlData
from cl_sii.dte.parse import DTE_XMLNS_MAP
from cl_sii.libs import crypto_utils, encoding_utils, tz_utils, xml_utils
Expand All @@ -37,10 +38,14 @@

logger = logging.getLogger(__name__)


_AEC_XML_SCHEMA_STR_PATH_TEMPLATE = (
"data/ref/factura_electronica/schemas-xml/{}/AEC_v10.xsd"
)
_AEC_XML_SCHEMA_PATH = Path(
Path(__file__).parent.parent,
Path('data/ref/factura_electronica/schemas-xml/AEC_v10.xsd'),
Path(
_AEC_XML_SCHEMA_STR_PATH_TEMPLATE.format(XmlSchemasVersionEnum.LATEST.value)
),
).resolve()

AEC_XML_SCHEMA_OBJ = xml_utils.read_xml_schema(str(_AEC_XML_SCHEMA_PATH))
Expand All @@ -59,14 +64,19 @@
# Main Functions
###############################################################################

def validate_aec_xml(xml_doc: XmlElement) -> None:
def validate_aec_xml(
xml_doc: XmlElement,
aec_xml_schema_version: XmlSchemasVersionEnum = XmlSchemasVersionEnum.LATEST
) -> None:
"""
Validate ``xml_doc`` against AEC's XML schema.
:raises xml_utils.XmlSchemaDocValidationError:
"""

aec_xml_schema_obj = _get_aec_xml_schema_obj(aec_xml_schema_version)
# TODO: Add better and more precise exception handling.
xml_utils.validate_xml_doc(AEC_XML_SCHEMA_OBJ, xml_doc)
xml_utils.validate_xml_doc(aec_xml_schema_obj, xml_doc, aec_xml_schema_version.value)


def parse_aec_xml(xml_doc: XmlElement) -> data_models_aec.AecXml:
Expand All @@ -84,6 +94,23 @@ def parse_aec_xml(xml_doc: XmlElement) -> data_models_aec.AecXml:
# Parser Functions and Models
###############################################################################

def _get_aec_xml_schema_obj(aec_xml_schema_version: XmlSchemasVersionEnum) -> XmlSchema:

if(aec_xml_schema_version is XmlSchemasVersionEnum.LATEST):
aec_xml_schema_obj = AEC_XML_SCHEMA_OBJ
else:
aec_xml_schema_path = Path(
Path(__file__).parent.parent,
Path(
_AEC_XML_SCHEMA_STR_PATH_TEMPLATE.format(aec_xml_schema_version.value)
),
).resolve()

aec_xml_schema_obj = xml_utils.read_xml_schema(str(aec_xml_schema_path))

return aec_xml_schema_obj


def _validate_rut(v: object) -> object:
"""
Reusable Pydantic validator for fields of type :class:`Rut`.
Expand Down
18 changes: 12 additions & 6 deletions tests/test_dte_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ def test_validate_dte_xml_fail_dte_1(self) -> None:
self.assertSequenceEqual(
cm.exception.args,
("Element 'DTE': No matching global declaration available for the validation root., "
"line 2", )
"line 2. "
f"XML schemas version {cl_sii.dte.constants.XmlSchemasVersionEnum.LATEST.value}",)
)

def test_validate_dte_xml_fail_dte_2(self) -> None:
Expand All @@ -104,7 +105,8 @@ def test_validate_dte_xml_fail_dte_2(self) -> None:
self.assertSequenceEqual(
cm.exception.args,
("Element 'DTE': No matching global declaration available for the validation root., "
"line 2", )
"line 2. "
f"XML schemas version {cl_sii.dte.constants.XmlSchemasVersionEnum.LATEST.value}",)
)

def test_validate_dte_xml_fail_dte_3(self) -> None:
Expand All @@ -120,7 +122,8 @@ def test_validate_dte_xml_fail_dte_3(self) -> None:
self.assertSequenceEqual(
cm.exception.args,
("Element 'DTE': No matching global declaration available for the validation root., "
"line 2", )
"line 2. "
f"XML schemas version {cl_sii.dte.constants.XmlSchemasVersionEnum.LATEST.value}",)
)


Expand Down Expand Up @@ -150,7 +153,8 @@ def test_clean_dte_xml_ok_1(self) -> None:
self.assertSequenceEqual(
cm.exception.args,
("Element 'DTE': No matching global declaration available for the validation root., "
"line 2", )
"line 2. "
f"XML schemas version {cl_sii.dte.constants.XmlSchemasVersionEnum.LATEST.value}",)
)

xml_doc_cleaned, modified = clean_dte_xml(
Expand Down Expand Up @@ -224,7 +228,8 @@ def test_clean_dte_xml_ok_2(self) -> None:
self.assertSequenceEqual(
cm.exception.args,
("Element 'DTE': No matching global declaration available for the validation root., "
"line 2", )
"line 2. "
f"XML schemas version {cl_sii.dte.constants.XmlSchemasVersionEnum.LATEST.value}",)
)

xml_doc_cleaned, modified = clean_dte_xml(
Expand Down Expand Up @@ -298,7 +303,8 @@ def test_clean_dte_xml_ok_3(self) -> None:
self.assertSequenceEqual(
cm.exception.args,
("Element 'DTE': No matching global declaration available for the validation root., "
"line 2", )
"line 2. "
f"XML schemas version {cl_sii.dte.constants.XmlSchemasVersionEnum.LATEST.value}",)
)

xml_doc_cleaned, modified = clean_dte_xml(
Expand Down

0 comments on commit acc4ce0

Please sign in to comment.