diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 476ccaa3..c7dba797 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.26.0 +current_version = 0.27.0 commit = True tag = False message = chore: Bump version from {current_version} to {new_version} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5839e4f2..0820b911 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: run: make python-virtualenv PYTHON_VIRTUALENV_DIR="venv" - name: Restoring/Saving Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: "venv" key: py-v1-deps-${{ runner.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} @@ -83,7 +83,7 @@ jobs: python-version: "${{ matrix.python_version }}" - name: Restoring/Saving Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: "venv" key: py-v1-deps-${{ runner.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} @@ -122,7 +122,7 @@ jobs: make test-coverage-report - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} directory: ./test-reports/coverage/ @@ -137,7 +137,7 @@ jobs: - name: Store Artifacts if: ${{ always() }} - uses: actions/upload-artifact@v4.0.0 + uses: actions/upload-artifact@v4.3.1 with: name: test_reports_${{ matrix.python_version }} path: test-reports/ diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml index dcea71d1..7c3ea70c 100644 --- a/.github/workflows/dependency-review.yaml +++ b/.github/workflows/dependency-review.yaml @@ -24,6 +24,6 @@ jobs: uses: actions/checkout@v4.1.1 - name: Dependency Review - uses: actions/dependency-review-action@v3.1.4 + uses: actions/dependency-review-action@v4.0.0 with: fail-on-severity: critical diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index ead87069..99976c0a 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -47,13 +47,13 @@ jobs: python-version: "3.10.9" - name: Restoring/Saving Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: "venv" key: py-v1-deps-${{ runner.os }}-${{ steps.set_up_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} - name: Restore Artifacts (Release) - uses: actions/download-artifact@v4.1.0 + uses: actions/download-artifact@v4.1.2 with: name: release path: ${{ inputs.artifacts_path }}/ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 46d9bcb1..5e603c19 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -47,7 +47,7 @@ jobs: run: make python-virtualenv PYTHON_VIRTUALENV_DIR="venv" - name: Restoring/Saving Cache - uses: actions/cache@v3.3.2 + uses: actions/cache@v4.0.0 with: path: "venv" key: py-v1-deps-${{ runner.os }}-${{ steps.set_up_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} @@ -68,7 +68,7 @@ jobs: make dist - name: Store Artifacts - uses: actions/upload-artifact@v4.0.0 + uses: actions/upload-artifact@v4.3.1 with: name: release path: ${{ env.ARTIFACTS_PATH }}/ diff --git a/HISTORY.md b/HISTORY.md index 99bc14d0..f20f42f4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,18 @@ # History +## 0.27.0 (2024-02-07) + +- (PR #593, 2024-02-01) chore: Bump pydantic from 2.5.3 to 2.6.0 +- (PR #592, 2024-02-05) chore: Bump lxml from 4.9.2 to 4.9.4 +- (PR #595, 2024-02-05) chore: Bump the development-dependencies group with 6 updates +- (PR #596, 2024-02-05) chore: Bump the production-dependencies group with 5 updates +- (PR #599, 2024-02-07) chore: Bump signxml from 3.2.1 to 3.2.2 +- (PR #598, 2024-02-07) chore: Bump pydantic from 2.6.0 to 2.6.1 +- (PR #600, 2024-02-07) chore: Bump pyopenssl from 23.2.0 to 24.0.0 +- (PR #597, 2024-02-07) chore: Bump pytz from 2023.3.post1 to 2024.1 +- (PR #601, 2024-02-07) chore: Bump marshmallow from 3.20.1 to 3.20.2 +- (PR #602, 2024-02-07) chore(deps): Update `lxml` from 4.9.4 to 5.1.0 + ## 0.26.0 (2024-01-30) - (PR #586, 2024-01-30) fix: Add default value for missing code `098` in SII CTE Form 29 diff --git a/requirements-dev.in b/requirements-dev.in index 2d021c81..24b7f92d 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -4,16 +4,16 @@ -c requirements.txt -black==23.12.1 +black==24.1.1 bumpversion==0.5.3 -coverage==7.4.0 -flake8==6.1.0 +coverage==7.4.1 +flake8==7.0.0 isort==5.13.2 mypy==1.8.0 pip-tools==7.3.0 tox==4.11.4 twine==4.0.2 -types-jsonschema==4.20.0.0 -types-pyOpenSSL==23.3.0.0 -types-pytz==2023.3.1.1 +types-jsonschema==4.21.0.20240118 +types-pyOpenSSL==24.0.0.20240130 +types-pytz==2024.1.0.20240203 wheel==0.42.0 diff --git a/requirements-dev.txt b/requirements-dev.txt index d5d23e5e..6516c209 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ attrs==23.1.0 # via # -c requirements.txt # referencing -black==23.12.1 +black==24.1.1 # via -r requirements-dev.in bleach==5.0.1 # via readme-renderer @@ -36,7 +36,7 @@ click==8.0.3 # pip-tools colorama==0.4.6 # via tox -coverage==7.4.0 +coverage==7.4.1 # via -r requirements-dev.in cryptography==41.0.7 # via @@ -51,7 +51,7 @@ filelock==3.12.4 # via # tox # virtualenv -flake8==6.1.0 +flake8==7.0.0 # via -r requirements-dev.in idna==2.10 # via requests @@ -106,7 +106,7 @@ pycparser==2.20 # via # -c requirements.txt # cffi -pyflakes==3.1.0 +pyflakes==3.2.0 # via flake8 pygments==2.15.0 # via @@ -153,11 +153,11 @@ tox==4.11.4 # via -r requirements-dev.in twine==4.0.2 # via -r requirements-dev.in -types-jsonschema==4.20.0.0 +types-jsonschema==4.21.0.20240118 # via -r requirements-dev.in -types-pyopenssl==23.3.0.0 +types-pyopenssl==24.0.0.20240130 # via -r requirements-dev.in -types-pytz==2023.3.1.1 +types-pytz==2024.1.0.20240203 # via -r requirements-dev.in typing-extensions==4.7.1 # via diff --git a/requirements.in b/requirements.in index 647232d7..313df023 100644 --- a/requirements.in +++ b/requirements.in @@ -11,9 +11,9 @@ Django>=2.2.24 djangorestframework>=3.10.3,<3.15 importlib-metadata==6.1.0 jsonschema==4.21.1 -lxml==4.9.2 -marshmallow==3.20.1 -pydantic==2.5.3 -pyOpenSSL==23.2.0 -pytz==2023.3.post1 -signxml==3.2.1 +lxml==5.1.0 +marshmallow==3.20.2 +pydantic==2.6.1 +pyOpenSSL==24.0.0 +pytz==2024.1 +signxml==3.2.2 diff --git a/requirements.txt b/requirements.txt index 1c40be74..4c5cd68f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,11 +39,11 @@ jsonschema==4.21.1 # via -r requirements.in jsonschema-specifications==2023.7.1 # via jsonschema -lxml==4.9.2 +lxml==5.1.0 # via # -r requirements.in # signxml -marshmallow==3.20.1 +marshmallow==3.20.2 # via -r requirements.in packaging==23.1 # via marshmallow @@ -51,15 +51,15 @@ pkgutil-resolve-name==1.3.10 # via jsonschema pycparser==2.20 # via cffi -pydantic==2.5.3 +pydantic==2.6.1 # via -r requirements.in -pydantic-core==2.14.6 +pydantic-core==2.16.2 # via pydantic -pyopenssl==23.2.0 +pyopenssl==24.0.0 # via # -r requirements.in # signxml -pytz==2023.3.post1 +pytz==2024.1 # via # -r requirements.in # django @@ -72,7 +72,7 @@ rpds-py==0.10.6 # via # jsonschema # referencing -signxml==3.2.1 +signxml==3.2.2 # via -r requirements.in sqlparse==0.4.4 # via django diff --git a/setup.py b/setup.py index 838138ae..6d84bfb1 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ def get_version(*file_paths: str) -> str: 'cryptography>=38.0.0', 'defusedxml>=0.6.0,<1', 'jsonschema>=3.1.1', - 'lxml>=4.6.5,<5', + 'lxml>=4.6.5,<6', 'marshmallow>=3,<4', 'pydantic>=2.3.0,!=1.7.*,!=1.8.*,!=1.9.*', 'pyOpenSSL>=22.0.0', diff --git a/src/cl_sii/__init__.py b/src/cl_sii/__init__.py index c1042412..1164314c 100644 --- a/src/cl_sii/__init__.py +++ b/src/cl_sii/__init__.py @@ -4,5 +4,4 @@ """ - -__version__ = '0.26.0' +__version__ = '0.27.0' diff --git a/src/cl_sii/base/constants.py b/src/cl_sii/base/constants.py index 28d85736..c019bb47 100644 --- a/src/cl_sii/base/constants.py +++ b/src/cl_sii/base/constants.py @@ -3,6 +3,7 @@ ================ """ + import enum import pytz @@ -17,7 +18,6 @@ @enum.unique class TipoDocumento(enum.IntEnum): - """ Enum of "Tipo de Documento". diff --git a/src/cl_sii/contribuyente/constants.py b/src/cl_sii/contribuyente/constants.py index 31bda97d..65e81b23 100644 --- a/src/cl_sii/contribuyente/constants.py +++ b/src/cl_sii/contribuyente/constants.py @@ -7,7 +7,6 @@ """ - # TODO: RAZON_SOCIAL_LONG_REGEX = re.compile(r'^...$') RAZON_SOCIAL_LONG_MAX_LENGTH = 100 diff --git a/src/cl_sii/dte/constants.py b/src/cl_sii/dte/constants.py index 2c2e4c75..5d57a2d4 100644 --- a/src/cl_sii/dte/constants.py +++ b/src/cl_sii/dte/constants.py @@ -5,6 +5,7 @@ https://github.com/fyntex/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/ """ + import enum from datetime import date from typing import FrozenSet @@ -87,7 +88,6 @@ @enum.unique class TipoDte(enum.IntEnum): - """ Enum of "Tipo de DTE". @@ -351,7 +351,6 @@ def receptor_is_vendedor(self) -> bool: @enum.unique class CodigoReferencia(enum.IntEnum): - """ Enum of "Código de referencia". diff --git a/src/cl_sii/dte/data_models.py b/src/cl_sii/dte/data_models.py index 192a7fcc..c3a32a4e 100644 --- a/src/cl_sii/dte/data_models.py +++ b/src/cl_sii/dte/data_models.py @@ -15,6 +15,7 @@ It *usually* corresponds to the DTE's "receptor", but not always. """ + from __future__ import annotations import dataclasses @@ -105,7 +106,6 @@ def validate_non_empty_bytes(value: bytes) -> None: ), ) class DteNaturalKey: - """ Natural key of a DTE. @@ -176,7 +176,6 @@ def validate_folio(cls, v: object) -> object: ), ) class DteDataL0(DteNaturalKey): - """ DTE data level 0. @@ -214,7 +213,6 @@ def natural_key(self) -> DteNaturalKey: ), ) class DteDataL1(DteDataL0): - """ DTE data level 1. @@ -329,7 +327,6 @@ def validate_monto_total(cls, v: object, info: pydantic.ValidationInfo) -> objec ), ) class DteDataL2(DteDataL1): - """ DTE data level 2. @@ -653,7 +650,6 @@ def validate_razon_ref(cls, value: str | None) -> str | None: ), ) class DteXmlData(DteDataL1): - """ DTE XML data. diff --git a/src/cl_sii/extras/dj_form_fields.py b/src/cl_sii/extras/dj_form_fields.py index 579f63e2..829f7e5b 100644 --- a/src/cl_sii/extras/dj_form_fields.py +++ b/src/cl_sii/extras/dj_form_fields.py @@ -2,6 +2,7 @@ cl_sii "extras" / Django form fields. """ + try: import django except ImportError as exc: # pragma: no cover @@ -17,7 +18,6 @@ class RutField(django.forms.CharField): - """ Django form field for RUT. diff --git a/src/cl_sii/extras/dj_model_fields.py b/src/cl_sii/extras/dj_model_fields.py index 28cee2b8..cc2ee719 100644 --- a/src/cl_sii/extras/dj_model_fields.py +++ b/src/cl_sii/extras/dj_model_fields.py @@ -2,6 +2,7 @@ cl_sii "extras" / Django model fields. """ + try: import django except ImportError as exc: # pragma: no cover @@ -18,7 +19,6 @@ class RutField(django.db.models.Field): - """ Django model field for RUT. diff --git a/src/cl_sii/extras/drf_fields.py b/src/cl_sii/extras/drf_fields.py index 9cbe7488..65383f33 100644 --- a/src/cl_sii/extras/drf_fields.py +++ b/src/cl_sii/extras/drf_fields.py @@ -4,6 +4,7 @@ (for serializers) """ + try: import rest_framework except ImportError as exc: # pragma: no cover @@ -15,7 +16,6 @@ class RutField(rest_framework.fields.CharField): - """ DRF field for RUT. diff --git a/src/cl_sii/extras/mm_fields.py b/src/cl_sii/extras/mm_fields.py index a85bcf56..dfb2de42 100644 --- a/src/cl_sii/extras/mm_fields.py +++ b/src/cl_sii/extras/mm_fields.py @@ -4,6 +4,7 @@ (for serializers) """ + from __future__ import annotations @@ -24,7 +25,6 @@ class RutField(marshmallow.fields.Field): - """ Marshmallow field for RUT. @@ -77,7 +77,6 @@ def _validated(self, value: Optional[object]) -> Optional[Rut]: class TipoDteField(marshmallow.fields.Field): - """ Marshmallow field for a DTE's "tipo DTE". @@ -136,7 +135,6 @@ def _validated(self, value: Optional[object]) -> Optional[TipoDte]: class RcvTipoDoctoField(marshmallow.fields.Field): - """ Marshmallow field for RCV's "tipo documento". diff --git a/src/cl_sii/libs/crypto_utils.py b/src/cl_sii/libs/crypto_utils.py index a362ecfe..51d6edab 100644 --- a/src/cl_sii/libs/crypto_utils.py +++ b/src/cl_sii/libs/crypto_utils.py @@ -37,6 +37,7 @@ > base64 encoding of the DER certificate [plus the header and footer]. """ + import base64 from typing import Union diff --git a/src/cl_sii/libs/dataclass_utils.py b/src/cl_sii/libs/dataclass_utils.py index 55ff9135..1eae6538 100644 --- a/src/cl_sii/libs/dataclass_utils.py +++ b/src/cl_sii/libs/dataclass_utils.py @@ -5,13 +5,13 @@ Utils for std lib's :class:`dataclasses.Dataclass` classes and instances. """ + import dataclasses import enum @enum.unique class DcDeepComparison(enum.IntEnum): - """ The possible results of a "deep comparison" between 2 dataclass instances. @@ -50,7 +50,6 @@ class DcDeepComparison(enum.IntEnum): class DcDeepCompareMixin: - """ Mixin for dataclass instances "deep comparison". """ diff --git a/src/cl_sii/libs/rows_processing.py b/src/cl_sii/libs/rows_processing.py index 082d4395..fc9fda90 100644 --- a/src/cl_sii/libs/rows_processing.py +++ b/src/cl_sii/libs/rows_processing.py @@ -9,7 +9,6 @@ class MaxRowsExceeded(RuntimeError): - """ The maximum number of rows has been exceeded. """ diff --git a/src/cl_sii/libs/tz_utils.py b/src/cl_sii/libs/tz_utils.py index 0fe59009..a64f5d98 100644 --- a/src/cl_sii/libs/tz_utils.py +++ b/src/cl_sii/libs/tz_utils.py @@ -10,6 +10,7 @@ `docs `_. """ + from datetime import datetime from typing import Optional, Union diff --git a/src/cl_sii/libs/xml_utils.py b/src/cl_sii/libs/xml_utils.py index 2ff7d1fa..550818ee 100644 --- a/src/cl_sii/libs/xml_utils.py +++ b/src/cl_sii/libs/xml_utils.py @@ -19,6 +19,7 @@ """ + import io import logging import os @@ -69,14 +70,12 @@ class BaseXmlParsingError(Exception): - """ Base class for all XML parsing errors. """ class XmlSyntaxError(BaseXmlParsingError): - """ The value to be parsed is syntactically invalid XML. @@ -87,7 +86,6 @@ class XmlSyntaxError(BaseXmlParsingError): class XmlFeatureForbidden(BaseXmlParsingError): - """ The parsed XML contains/uses a feature that is forbidden. @@ -101,7 +99,6 @@ class XmlFeatureForbidden(BaseXmlParsingError): class UnknownXmlParsingError(BaseXmlParsingError): - """ An unkwnown XML parsing error or for which there is no handling implementation. @@ -113,7 +110,6 @@ class UnknownXmlParsingError(BaseXmlParsingError): class XmlSchemaDocValidationError(Exception): - """ XML document did not be validate against an XML schema. @@ -121,14 +117,12 @@ class XmlSchemaDocValidationError(Exception): class XmlSignatureInvalid(Exception): - """ XML signature is invalid, for any reason. """ class XmlSignatureUnverified(XmlSignatureInvalid): - """ XML signature verification (i.e. digest validation) failed. @@ -137,7 +131,6 @@ class XmlSignatureUnverified(XmlSignatureInvalid): class XmlSignatureInvalidCertificate(XmlSignatureInvalid): - """ Certificate validation failed on XML signature processing. """ diff --git a/src/cl_sii/rcv/constants.py b/src/cl_sii/rcv/constants.py index b3ecab1b..ed87ac26 100644 --- a/src/cl_sii/rcv/constants.py +++ b/src/cl_sii/rcv/constants.py @@ -99,7 +99,6 @@ class RcEstadoContable(enum.Enum): @enum.unique class RcvTipoDocto(enum.IntEnum): - """ Enum of "Tipo de Documento" for the RCV domain. diff --git a/src/cl_sii/rcv/data_models.py b/src/cl_sii/rcv/data_models.py index 6a8bec5e..f9fc3b3b 100644 --- a/src/cl_sii/rcv/data_models.py +++ b/src/cl_sii/rcv/data_models.py @@ -4,6 +4,7 @@ """ + from __future__ import annotations import logging @@ -106,7 +107,6 @@ def as_datetime(self) -> datetime: ), ) class RcvDetalleEntry: - """ Entry of the "detalle" of an RCV. """ @@ -237,7 +237,6 @@ def as_dte_data_l2(self) -> cl_sii.dte.data_models.DteDataL2: ), ) class RvDetalleEntry(RcvDetalleEntry): - """ Entry of the "detalle" of an RV ("Registro de Ventas"). """ @@ -289,7 +288,6 @@ def validate_datetime_tz(cls, v: object) -> object: ), ) class RcRegistroDetalleEntry(RcvDetalleEntry): - """ Entry of the "detalle" of an RC ("Registro de Compras") / "registro". """ @@ -338,7 +336,6 @@ def validate_datetime_tz(cls, v: object) -> object: ), ) class RcNoIncluirDetalleEntry(RcRegistroDetalleEntry): - """ Entry of the "detalle" of an RC ("Registro de Compras") / "no incluir". """ @@ -354,7 +351,6 @@ class RcNoIncluirDetalleEntry(RcRegistroDetalleEntry): ), ) class RcReclamadoDetalleEntry(RcvDetalleEntry): - """ Entry of the "detalle" of an RC ("Registro de Compras") / "reclamado". """ @@ -407,7 +403,6 @@ def validate_datetime_tz(cls, v: object) -> object: ), ) class RcPendienteDetalleEntry(RcvDetalleEntry): - """ Entry of the "detalle" of an RC ("Registro de Compras") / "pendiente". """ diff --git a/src/cl_sii/rcv/parse_csv.py b/src/cl_sii/rcv/parse_csv.py index 7b57b5dc..e5e2a7b7 100644 --- a/src/cl_sii/rcv/parse_csv.py +++ b/src/cl_sii/rcv/parse_csv.py @@ -4,6 +4,7 @@ """ + import csv import logging from datetime import date, datetime @@ -1086,7 +1087,6 @@ def to_detalle_entry(self, data: dict) -> RcPendienteDetalleEntry: class _RcvCsvDialect(csv.Dialect): - """ CSV dialect of RCV CSV files. diff --git a/src/cl_sii/rtc/constants.py b/src/cl_sii/rtc/constants.py index 37d58e09..17e2b34b 100644 --- a/src/cl_sii/rtc/constants.py +++ b/src/cl_sii/rtc/constants.py @@ -66,7 +66,6 @@ @enum.unique class RolContribuyenteEnCesion(enum.Enum): - """ "Rol" of "contribuyente" in a "cesion". """ diff --git a/src/cl_sii/rut/__init__.py b/src/cl_sii/rut/__init__.py index c5aeb29e..4b12b74e 100644 --- a/src/cl_sii/rut/__init__.py +++ b/src/cl_sii/rut/__init__.py @@ -9,6 +9,7 @@ ``'76042235-5'``, ``'96874030-K'``. """ + import itertools import random import re @@ -17,7 +18,6 @@ class Rut: - """ Representation of a RUT. diff --git a/src/cl_sii/rut/constants.py b/src/cl_sii/rut/constants.py index 832dc2b8..b8adb364 100644 --- a/src/cl_sii/rut/constants.py +++ b/src/cl_sii/rut/constants.py @@ -5,6 +5,7 @@ https://github.com/fyntex/lib-cl-sii-python/blob/f57a326/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L127-L136 """ + import re import cryptography.x509 diff --git a/src/tests/test_libs_xml_utils.py b/src/tests/test_libs_xml_utils.py index 2ab5c33c..e30c7f45 100644 --- a/src/tests/test_libs_xml_utils.py +++ b/src/tests/test_libs_xml_utils.py @@ -51,17 +51,21 @@ def test_attack_billion_laughs_1(self) -> None: self.assertSequenceEqual( cm.exception.args, - ("XML syntax error. Detected an entity reference loop, line 1, column 7.",), + ( + "XML syntax error." + " Maximum entity amplification factor exceeded, see xmlCtxtSetMaxAmplification.," + " line 1, column 25.", + ), ) def test_attack_billion_laughs_2(self) -> None: value = read_test_file_bytes('test_data/xml/attacks/billion-laughs-2.xml') - with self.assertRaises(XmlSyntaxError) as cm: + with self.assertRaises(XmlFeatureForbidden) as cm: parse_untrusted_xml(value) self.assertSequenceEqual( cm.exception.args, - ("XML syntax error. Detected an entity reference loop, line 1, column 4.",), + ("XML uses or contains a forbidden feature.",), ) def test_attack_quadratic_blowup(self) -> None: