From 0124be26e0119b90ea0bc5226f8f46a5bbe161c7 Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Wed, 25 Sep 2024 18:58:42 -0300 Subject: [PATCH 1/3] chore(deps): Install Python package `types-lxml` > Complete lxml external type annotation - [Web Site](https://github.com/abelcheung/types-lxml/) - [VCS Repository](https://github.com/abelcheung/types-lxml.git) - [Documentation](https://github.com/abelcheung/types-lxml/blob/694a3553/README.md) - [Software Repository](https://pypi.org/project/types-lxml/) --- requirements-dev.in | 1 + requirements-dev.txt | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/requirements-dev.in b/requirements-dev.in index 1805d67e..9b49d678 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -15,6 +15,7 @@ pip-tools==7.4.1 tox==4.20.0 twine==5.1.1 types-jsonschema==4.23.0.20240813 +types-lxml==2024.9.16 types-pyOpenSSL==24.1.0.20240722 types-pytz==2024.2.0.20240913 wheel==0.44.0 diff --git a/requirements-dev.txt b/requirements-dev.txt index 2f39f580..61de4934 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -45,6 +45,8 @@ cryptography==43.0.1 # -c requirements.txt # secretstorage # types-pyopenssl +cssselect==1.2.0 + # via types-lxml distlib==0.3.7 # via virtualenv docutils==0.19 @@ -157,10 +159,16 @@ tox==4.20.0 # via -r requirements-dev.in twine==5.1.1 # via -r requirements-dev.in +types-beautifulsoup4==4.12.0.20240907 + # via types-lxml types-cffi==1.16.0.20240331 # via types-pyopenssl +types-html5lib==1.1.11.20240806 + # via types-beautifulsoup4 types-jsonschema==4.23.0.20240813 # via -r requirements-dev.in +types-lxml==2024.9.16 + # via -r requirements-dev.in types-pyopenssl==24.1.0.20240722 # via -r requirements-dev.in types-pytz==2024.2.0.20240913 @@ -173,6 +181,7 @@ typing-extensions==4.12.2 # black # mypy # rich + # types-lxml urllib3==1.26.19 # via # requests From c43f0e394fc36afdcc13a3b93faedf07d45266e9 Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Wed, 25 Sep 2024 19:00:59 -0300 Subject: [PATCH 2/3] chore: Enable type checking for `lxml` --- mypy.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index 5f154628..6cadfb87 100644 --- a/mypy.ini +++ b/mypy.ini @@ -32,9 +32,6 @@ ignore_missing_imports = True [mypy-django_filters.*] ignore_missing_imports = True -[mypy-lxml.*] -ignore_missing_imports = True - [mypy-rest_framework.*] ignore_missing_imports = True From a70ac2196d2120756de7ba2e8efb7ab799b57e77 Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Wed, 25 Sep 2024 19:14:47 -0300 Subject: [PATCH 3/3] fix: Fix errors reported by Mypy after enabling type checking for `lxml` --- src/cl_sii/dte/parse.py | 12 ++++++++++-- src/cl_sii/libs/xml_utils.py | 6 +++++- src/cl_sii/rtc/parse_aec.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/cl_sii/dte/parse.py b/src/cl_sii/dte/parse.py index 2639ffa7..0b5ddf80 100644 --- a/src/cl_sii/dte/parse.py +++ b/src/cl_sii/dte/parse.py @@ -163,6 +163,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: 'ds:Signature', # "Firma Digital sobre Documento" namespaces=xml_utils.XML_DSIG_NS_MAP, ) + assert signature_em is not None if liquidacion_em is not None or exportaciones_em is not None: raise NotImplementedError("XML element 'Documento' is the only one supported.") @@ -191,6 +192,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: 'sii-dte:Encabezado', # "Identificacion y Totales del Documento" namespaces=DTE_XMLNS_MAP, ) + assert encabezado_em is not None # note: excluded because currently it is not useful. # ted_em = documento_em.find( # 'sii-dte:TED', # "Timbre Electronico de DTE" @@ -215,18 +217,22 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: 'sii-dte:IdDoc', # "Identificacion del DTE" namespaces=DTE_XMLNS_MAP, ) + assert id_doc_em is not None emisor_em = encabezado_em.find( 'sii-dte:Emisor', # "Datos del Emisor" namespaces=DTE_XMLNS_MAP, ) + assert emisor_em is not None receptor_em = encabezado_em.find( 'sii-dte:Receptor', # "Datos del Receptor" namespaces=DTE_XMLNS_MAP, ) + assert receptor_em is not None totales_em = encabezado_em.find( 'sii-dte:Totales', # "Montos Totales del DTE" namespaces=DTE_XMLNS_MAP, ) + assert totales_em is not None # 'Documento.Encabezado.IdDoc' # Excluded elements (optional according to the XML schema but the SII may require some of these @@ -453,6 +459,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: 'ds:KeyInfo', # "Informacion de Claves Publicas y Certificado" namespaces=xml_utils.XML_DSIG_NS_MAP, ) + assert signature_key_info_em is not None # signature_key_info_key_value_em = signature_key_info_em.find( # 'ds:KeyValue', # namespaces=xml_utils.XML_DSIG_NS_MAP) @@ -460,6 +467,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: 'ds:X509Data', # "Informacion del Certificado Publico" namespaces=xml_utils.XML_DSIG_NS_MAP, ) + assert signature_key_info_x509_data_em is not None signature_key_info_x509_cert_em = signature_key_info_x509_data_em.find( 'ds:X509Certificate', # "Certificado Publico" namespaces=xml_utils.XML_DSIG_NS_MAP, @@ -523,7 +531,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData: ) -def _text_strip_or_none(xml_em: XmlElement) -> Optional[str]: +def _text_strip_or_none(xml_em: Optional[XmlElement]) -> Optional[str]: # note: we need the pair of functions '_text_strip_or_none' and '_text_strip_or_raise' # because, under certain circumstances, an XML tag: # - with no content -> `xml_em.text` is None instead of '' @@ -539,7 +547,7 @@ def _text_strip_or_none(xml_em: XmlElement) -> Optional[str]: return stripped_text -def _text_strip_or_raise(xml_em: XmlElement) -> str: +def _text_strip_or_raise(xml_em: Optional[XmlElement]) -> str: # note: we need the pair of functions '_text_strip_or_none' and '_text_strip_or_raise' # because, under certain circumstances, an XML tag: # - with no content -> `xml_em.text` is None instead of '' diff --git a/src/cl_sii/libs/xml_utils.py b/src/cl_sii/libs/xml_utils.py index c7b01354..da4be0fd 100644 --- a/src/cl_sii/libs/xml_utils.py +++ b/src/cl_sii/libs/xml_utils.py @@ -361,7 +361,11 @@ def verify_xml_signature( trusted_x509_cert: Optional[Union[crypto_utils.X509Cert, crypto_utils._X509CertOpenSsl]] = None, xml_verifier: Optional[signxml.verifier.XMLVerifier] = None, xml_verifier_supports_multiple_signatures: bool = False, -) -> Tuple[bytes, XmlElementTree, XmlElementTree]: +) -> Tuple[ + bytes, + Optional[Union[XmlElementTree, lxml.etree._Element]], + Union[XmlElementTree, lxml.etree._Element], +]: """ Verify the XML signature in ``xml_doc``. diff --git a/src/cl_sii/rtc/parse_aec.py b/src/cl_sii/rtc/parse_aec.py index 6591b10a..d1098886 100644 --- a/src/cl_sii/rtc/parse_aec.py +++ b/src/cl_sii/rtc/parse_aec.py @@ -134,11 +134,13 @@ def parse_xml_to_dict(xml_em: XmlElement) -> Mapping[str, object]: """ # XPath: //Signature/KeyInfo key_info_em = xml_em.find('ds:KeyInfo', namespaces=xml_utils.XML_DSIG_NS_MAP) + assert key_info_em is not None # XPath: //Signature/KeyInfo/X509Data key_info_x509_data_em = key_info_em.find( 'ds:X509Data', namespaces=xml_utils.XML_DSIG_NS_MAP ) + assert key_info_x509_data_em is not None # XPath: //Signature return dict( @@ -474,14 +476,17 @@ def parse_xml_to_dict(xml_em: XmlElement) -> Mapping[str, object]: """ # XPath: /AEC/DocumentoAEC/Cesiones/Cesion/DocumentoCesion/IdDTE id_dte_em = xml_em.find('sii-dte:IdDTE', namespaces=DTE_XMLNS_MAP) + assert id_dte_em is not None id_dte_dict = _IdDte.parse_xml_to_dict(id_dte_em) # XPath: /AEC/DocumentoAEC/Cesiones/Cesion/DocumentoCesion/Cedente cedente_em = xml_em.find('sii-dte:Cedente', namespaces=DTE_XMLNS_MAP) + assert cedente_em is not None cedente_dict = _Cedente.parse_xml_to_dict(cedente_em) # XPath: /AEC/DocumentoAEC/Cesiones/Cesion/DocumentoCesion/Cesionario cesionario_em = xml_em.find('sii-dte:Cesionario', namespaces=DTE_XMLNS_MAP) + assert cesionario_em is not None cesionario_dict = _Cesionario.parse_xml_to_dict(cesionario_em) # XPath: /AEC/DocumentoAEC/Cesiones/Cesion/DocumentoCesion @@ -543,6 +548,7 @@ def parse_xml_to_dict(xml_em: XmlElement) -> Mapping[str, object]: """ # XPath: /AEC/DocumentoAEC/Cesiones/Cesion/DocumentoCesion doc_cesion_em = xml_em.find('sii-dte:DocumentoCesion', namespaces=DTE_XMLNS_MAP) + assert doc_cesion_em is not None doc_cesion_dict = _DocumentoCesion.parse_xml_to_dict(doc_cesion_em) # Signature over 'DocumentoCesion' @@ -689,6 +695,7 @@ def parse_xml_to_dict(xml_em: XmlElement) -> Mapping[str, object]: 'sii-dte:DocumentoDTECedido', namespaces=DTE_XMLNS_MAP, ) + assert doc_dte_cedido_em is not None # Signature over 'DocumentoDTECedido' # XPath: /AEC/DocumentoAEC/Cesiones/DTECedido/Signature @@ -819,13 +826,16 @@ def parse_xml_to_dict(xml_em: XmlElement) -> Mapping[str, object]: """ # XPath: /AEC/DocumentoAEC/Caratula caratula_em = xml_em.find('sii-dte:Caratula', namespaces=DTE_XMLNS_MAP) + assert caratula_em is not None caratula_dict = _Caratula.parse_xml_to_dict(caratula_em) # XPath: /AEC/DocumentoAEC/Cesiones cesiones_em = xml_em.find('sii-dte:Cesiones', namespaces=DTE_XMLNS_MAP) + assert cesiones_em is not None # XPath: /AEC/DocumentoAEC/Cesiones/DTECedido dte_cedido_em = cesiones_em.find('sii-dte:DTECedido', namespaces=DTE_XMLNS_MAP) + assert dte_cedido_em is not None dte_cedido_dict = _DteCedido.parse_xml_to_dict(dte_cedido_em) # XPath: /AEC/DocumentoAEC/Cesiones/Cesion @@ -919,6 +929,7 @@ def parse_xml_to_dict(xml_doc: XmlElement) -> Mapping[str, object]: # XPath: /AEC/DocumentoAEC doc_aec_em = aec_em.find('sii-dte:DocumentoAEC', namespaces=DTE_XMLNS_MAP) + assert doc_aec_em is not None doc_aec_dict = _DocumentoAec.parse_xml_to_dict(doc_aec_em) # Signature over 'DocumentoAEC' @@ -927,6 +938,7 @@ def parse_xml_to_dict(xml_doc: XmlElement) -> Mapping[str, object]: 'ds:Signature', namespaces=xml_utils.XML_DSIG_NS_MAP, ) + assert signature_over_doc_aec_em is not None signature_over_doc_aec_dict = _XmlSignature.parse_xml_to_dict(signature_over_doc_aec_em) # XPath: /AEC