Skip to content

Commit 9b7211c

Browse files
authored
Merge pull request #705 from cordada/deploy/v0.34.0
Deploy release v0.34.0
2 parents 27f1547 + a222b1c commit 9b7211c

File tree

13 files changed

+214
-25
lines changed

13 files changed

+214
-25
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.33.0
2+
current_version = 0.34.0
33
commit = True
44
tag = False
55
message = chore: Bump version from {current_version} to {new_version}

HISTORY.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# History
22

3+
## 0.34.0 (2024-09-26)
4+
5+
- (PR #690, 2024-09-25) chore(deps): Bump lxml from 5.2.2 to 5.3.0
6+
- (PR #691, 2024-09-25) chore(deps): Bump marshmallow from 3.21.3 to 3.22.0
7+
- (PR #701, 2024-09-25) Enable type checking for `setuptools`
8+
- (PR #702, 2024-09-25) Enable type checking for `lxml`
9+
- (PR #703, 2024-09-26) Relax some validations for trusted inputs
10+
311
## 0.33.0 (2024-09-24)
412

513
- (PR #689, 2024-09-24) chore(deps): Bump pydantic from 2.7.2 to 2.8.2

mypy.ini

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,9 @@ ignore_missing_imports = True
3232
[mypy-django_filters.*]
3333
ignore_missing_imports = True
3434

35-
[mypy-lxml.*]
36-
ignore_missing_imports = True
37-
3835
[mypy-rest_framework.*]
3936
ignore_missing_imports = True
4037

41-
[mypy-setuptools.*]
42-
ignore_missing_imports = True
43-
4438
[pydantic-mypy]
4539
init_forbid_extra = True
4640
init_typed = True

requirements-dev.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pip-tools==7.4.1
1515
tox==4.20.0
1616
twine==5.1.1
1717
types-jsonschema==4.23.0.20240813
18+
types-lxml==2024.9.16
1819
types-pyOpenSSL==24.1.0.20240722
1920
types-pytz==2024.2.0.20240913
2021
wheel==0.44.0

requirements-dev.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ cryptography==43.0.1
4545
# -c requirements.txt
4646
# secretstorage
4747
# types-pyopenssl
48+
cssselect==1.2.0
49+
# via types-lxml
4850
distlib==0.3.7
4951
# via virtualenv
5052
docutils==0.19
@@ -157,10 +159,16 @@ tox==4.20.0
157159
# via -r requirements-dev.in
158160
twine==5.1.1
159161
# via -r requirements-dev.in
162+
types-beautifulsoup4==4.12.0.20240907
163+
# via types-lxml
160164
types-cffi==1.16.0.20240331
161165
# via types-pyopenssl
166+
types-html5lib==1.1.11.20240806
167+
# via types-beautifulsoup4
162168
types-jsonschema==4.23.0.20240813
163169
# via -r requirements-dev.in
170+
types-lxml==2024.9.16
171+
# via -r requirements-dev.in
164172
types-pyopenssl==24.1.0.20240722
165173
# via -r requirements-dev.in
166174
types-pytz==2024.2.0.20240913
@@ -173,6 +181,7 @@ typing-extensions==4.12.2
173181
# black
174182
# mypy
175183
# rich
184+
# types-lxml
176185
urllib3==1.26.19
177186
# via
178187
# requests

requirements.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ Django>=2.2.24
1313
djangorestframework>=3.10.3,<3.16
1414
importlib-metadata==8.4.0
1515
jsonschema==4.23.0
16-
lxml==5.2.2
17-
marshmallow==3.21.3
16+
lxml==5.3.0
17+
marshmallow==3.22.0
1818
pydantic==2.9.2
1919
pyOpenSSL==24.2.1
2020
pytz==2024.1

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ jsonschema==4.23.0
4747
# via -r requirements.in
4848
jsonschema-specifications==2023.12.1
4949
# via jsonschema
50-
lxml==5.2.2
50+
lxml==5.3.0
5151
# via
5252
# -r requirements.in
5353
# signxml
54-
marshmallow==3.21.3
54+
marshmallow==3.22.0
5555
# via -r requirements.in
5656
packaging==24.1
5757
# via marshmallow

src/cl_sii/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
55
"""
66

7-
__version__ = '0.33.0'
7+
__version__ = '0.34.0'

src/cl_sii/dte/data_models.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from __future__ import annotations
2020

2121
import dataclasses
22+
import logging
2223
from datetime import date, datetime
2324
from typing import Mapping, Optional, Sequence
2425

@@ -33,6 +34,9 @@
3334
from .constants import CodigoReferencia, TipoDte
3435

3536

37+
logger = logging.getLogger(__name__)
38+
39+
3640
def validate_dte_folio(value: int) -> None:
3741
"""
3842
Validate value for DTE field ``folio``.
@@ -99,6 +103,39 @@ def validate_non_empty_bytes(value: bytes) -> None:
99103
raise ValueError("Bytes value length is 0.")
100104

101105

106+
VALIDATION_CONTEXT_TRUST_INPUT: str = 'trust_input'
107+
"""
108+
Key for the validation context to indicate that the input data is trusted.
109+
"""
110+
111+
112+
def is_input_trusted_according_to_validation_context(
113+
validation_context: Optional[Mapping[str, object]]
114+
) -> bool:
115+
"""
116+
Return whether the input data is trusted according to the validation context.
117+
118+
:param validation_context:
119+
The validation context of a Pydantic model.
120+
Get it from ``pydantic.ValidationInfo.context``.
121+
122+
Example for data classes:
123+
124+
>>> dte_xml_data_instance_kwargs: Mapping[str, object] = dict(
125+
... emisor_rut=Rut('60910000-1'), # ...
126+
... )
127+
>>> dte_xml_data_adapter = pydantic.TypeAdapter(DteXmlData)
128+
>>> dte_xml_data_instance: DteXmlData = dte_xml_data_adapter.validate_python(
129+
... dte_xml_data_instance_kwargs,
130+
... context={VALIDATION_CONTEXT_TRUST_INPUT: True}
131+
... )
132+
"""
133+
if validation_context is None:
134+
return False
135+
else:
136+
return validation_context.get(VALIDATION_CONTEXT_TRUST_INPUT) is True
137+
138+
102139
@pydantic.dataclasses.dataclass(
103140
frozen=True,
104141
config=pydantic.ConfigDict(
@@ -815,7 +852,9 @@ def validate_referencias_numero_linea_ref_order(cls, v: object) -> object:
815852
return v
816853

817854
@pydantic.model_validator(mode='after')
818-
def validate_referencias_rut_otro_is_consistent_with_tipo_dte(self) -> DteXmlData:
855+
def validate_referencias_rut_otro_is_consistent_with_tipo_dte(
856+
self, info: pydantic.ValidationInfo
857+
) -> DteXmlData:
819858
referencias = self.referencias
820859
tipo_dte = self.tipo_dte
821860

@@ -826,27 +865,37 @@ def validate_referencias_rut_otro_is_consistent_with_tipo_dte(self) -> DteXmlDat
826865
):
827866
for referencia in referencias:
828867
if referencia.rut_otro:
829-
raise ValueError(
868+
message: str = (
830869
f"Setting a 'rut_otro' is not a valid option for this 'tipo_dte':"
831870
f" 'tipo_dte' == {tipo_dte!r},"
832-
f" 'Referencia' number {referencia.numero_linea_ref}.",
871+
f" 'Referencia' number {referencia.numero_linea_ref}."
833872
)
873+
if is_input_trusted_according_to_validation_context(info.context):
874+
logger.warning('Validation failed but input is trusted: %s', message)
875+
else:
876+
raise ValueError(message)
834877

835878
return self
836879

837880
@pydantic.model_validator(mode='after')
838-
def validate_referencias_rut_otro_is_consistent_with_emisor_rut(self) -> DteXmlData:
881+
def validate_referencias_rut_otro_is_consistent_with_emisor_rut(
882+
self, info: pydantic.ValidationInfo
883+
) -> DteXmlData:
839884
referencias = self.referencias
840885
emisor_rut = self.emisor_rut
841886

842887
if isinstance(referencias, Sequence) and isinstance(emisor_rut, Rut):
843888
for referencia in referencias:
844889
if referencia.rut_otro and referencia.rut_otro == emisor_rut:
845-
raise ValueError(
890+
message: str = (
846891
f"'rut_otro' must be different from 'emisor_rut':"
847892
f" {referencia.rut_otro!r} == {emisor_rut!r},"
848-
f" 'Referencia' number {referencia.numero_linea_ref}.",
893+
f" 'Referencia' number {referencia.numero_linea_ref}."
849894
)
895+
if is_input_trusted_according_to_validation_context(info.context):
896+
logger.warning('Validation failed but input is trusted: %s', message)
897+
else:
898+
raise ValueError(message)
850899

851900
return self
852901

src/cl_sii/dte/parse.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData:
163163
'ds:Signature', # "Firma Digital sobre Documento"
164164
namespaces=xml_utils.XML_DSIG_NS_MAP,
165165
)
166+
assert signature_em is not None
166167

167168
if liquidacion_em is not None or exportaciones_em is not None:
168169
raise NotImplementedError("XML element 'Documento' is the only one supported.")
@@ -191,6 +192,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData:
191192
'sii-dte:Encabezado', # "Identificacion y Totales del Documento"
192193
namespaces=DTE_XMLNS_MAP,
193194
)
195+
assert encabezado_em is not None
194196
# note: excluded because currently it is not useful.
195197
# ted_em = documento_em.find(
196198
# 'sii-dte:TED', # "Timbre Electronico de DTE"
@@ -215,18 +217,22 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData:
215217
'sii-dte:IdDoc', # "Identificacion del DTE"
216218
namespaces=DTE_XMLNS_MAP,
217219
)
220+
assert id_doc_em is not None
218221
emisor_em = encabezado_em.find(
219222
'sii-dte:Emisor', # "Datos del Emisor"
220223
namespaces=DTE_XMLNS_MAP,
221224
)
225+
assert emisor_em is not None
222226
receptor_em = encabezado_em.find(
223227
'sii-dte:Receptor', # "Datos del Receptor"
224228
namespaces=DTE_XMLNS_MAP,
225229
)
230+
assert receptor_em is not None
226231
totales_em = encabezado_em.find(
227232
'sii-dte:Totales', # "Montos Totales del DTE"
228233
namespaces=DTE_XMLNS_MAP,
229234
)
235+
assert totales_em is not None
230236

231237
# 'Documento.Encabezado.IdDoc'
232238
# Excluded elements (optional according to the XML schema but the SII may require some of these
@@ -453,13 +459,15 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData:
453459
'ds:KeyInfo', # "Informacion de Claves Publicas y Certificado"
454460
namespaces=xml_utils.XML_DSIG_NS_MAP,
455461
)
462+
assert signature_key_info_em is not None
456463
# signature_key_info_key_value_em = signature_key_info_em.find(
457464
# 'ds:KeyValue',
458465
# namespaces=xml_utils.XML_DSIG_NS_MAP)
459466
signature_key_info_x509_data_em = signature_key_info_em.find(
460467
'ds:X509Data', # "Informacion del Certificado Publico"
461468
namespaces=xml_utils.XML_DSIG_NS_MAP,
462469
)
470+
assert signature_key_info_x509_data_em is not None
463471
signature_key_info_x509_cert_em = signature_key_info_x509_data_em.find(
464472
'ds:X509Certificate', # "Certificado Publico"
465473
namespaces=xml_utils.XML_DSIG_NS_MAP,
@@ -523,7 +531,7 @@ def parse_dte_xml(xml_doc: XmlElement) -> data_models.DteXmlData:
523531
)
524532

525533

526-
def _text_strip_or_none(xml_em: XmlElement) -> Optional[str]:
534+
def _text_strip_or_none(xml_em: Optional[XmlElement]) -> Optional[str]:
527535
# note: we need the pair of functions '_text_strip_or_none' and '_text_strip_or_raise'
528536
# because, under certain circumstances, an XML tag:
529537
# - with no content -> `xml_em.text` is None instead of ''
@@ -539,7 +547,7 @@ def _text_strip_or_none(xml_em: XmlElement) -> Optional[str]:
539547
return stripped_text
540548

541549

542-
def _text_strip_or_raise(xml_em: XmlElement) -> str:
550+
def _text_strip_or_raise(xml_em: Optional[XmlElement]) -> str:
543551
# note: we need the pair of functions '_text_strip_or_none' and '_text_strip_or_raise'
544552
# because, under certain circumstances, an XML tag:
545553
# - with no content -> `xml_em.text` is None instead of ''

0 commit comments

Comments
 (0)