From 346afc131cde1503d9cf1138c0c2a9957ae91c38 Mon Sep 17 00:00:00 2001 From: Christodoulos Tsoulloftas Date: Sun, 19 May 2024 15:16:49 +0300 Subject: [PATCH] fix: Avoid using not-threadsafe arnings.catch_warning --- .../handlers/test_disambiguate_choices.py | 1 - .../dataclass/parsers/nodes/test_primitive.py | 16 ++-- .../dataclass/parsers/nodes/test_standard.py | 20 +++-- tests/formats/dataclass/parsers/test_node.py | 2 +- tests/formats/dataclass/parsers/test_utils.py | 13 +++- tests/formats/dataclass/parsers/test_xml.py | 2 +- xsdata/formats/dataclass/parsers/bases.py | 14 ++-- xsdata/formats/dataclass/parsers/dict.py | 51 ++++-------- .../dataclass/parsers/nodes/element.py | 5 +- .../dataclass/parsers/nodes/primitive.py | 13 +++- .../dataclass/parsers/nodes/standard.py | 17 +++- .../formats/dataclass/parsers/nodes/union.py | 77 ++++++------------- xsdata/formats/dataclass/parsers/utils.py | 15 ++-- 13 files changed, 117 insertions(+), 129 deletions(-) diff --git a/tests/codegen/handlers/test_disambiguate_choices.py b/tests/codegen/handlers/test_disambiguate_choices.py index 60fe64c54..29101ab84 100644 --- a/tests/codegen/handlers/test_disambiguate_choices.py +++ b/tests/codegen/handlers/test_disambiguate_choices.py @@ -144,7 +144,6 @@ def test_disambiguate_choice_with_circular_ref(self): def test_find_ambiguous_choices_ignore_wildcards(self): """Wildcards are merged.""" - attr = AttrFactory.create() attr.choices.append(AttrFactory.any()) attr.choices.append(AttrFactory.any()) diff --git a/tests/formats/dataclass/parsers/nodes/test_primitive.py b/tests/formats/dataclass/parsers/nodes/test_primitive.py index ffddb3336..61c5b8c23 100644 --- a/tests/formats/dataclass/parsers/nodes/test_primitive.py +++ b/tests/formats/dataclass/parsers/nodes/test_primitive.py @@ -3,6 +3,7 @@ from tests.fixtures.artists import Artist from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.elements import XmlType +from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory @@ -12,6 +13,7 @@ class PrimitiveNodeTests(TestCase): def setUp(self): super().setUp() self.meta = XmlMetaFactory.create(clazz=Artist) + self.config = ParserConfig() @mock.patch.object(ParserUtils, "parse_var") def test_bind(self, mock_parse_var): @@ -20,14 +22,14 @@ def test_bind(self, mock_parse_var): xml_type=XmlType.TEXT, name="foo", types=(int,), format="Nope" ) ns_map = {"foo": "bar"} - node = PrimitiveNode(self.meta, var, ns_map) + node = PrimitiveNode(self.meta, var, ns_map, self.config) objects = [] self.assertTrue(node.bind("foo", "13", "Impossible", objects)) self.assertEqual(("foo", 13), objects[-1]) mock_parse_var.assert_called_once_with( - meta=self.meta, var=var, value="13", ns_map=ns_map + meta=self.meta, var=var, config=self.config, value="13", ns_map=ns_map ) def test_bind_nillable_content(self): @@ -35,7 +37,7 @@ def test_bind_nillable_content(self): xml_type=XmlType.TEXT, name="foo", types=(str,), nillable=False ) ns_map = {"foo": "bar"} - node = PrimitiveNode(self.meta, var, ns_map) + node = PrimitiveNode(self.meta, var, ns_map, self.config) objects = [] self.assertTrue(node.bind("foo", None, None, objects)) @@ -53,7 +55,7 @@ def test_bind_nillable_bytes_content(self): nillable=False, ) ns_map = {"foo": "bar"} - node = PrimitiveNode(self.meta, var, ns_map) + node = PrimitiveNode(self.meta, var, ns_map, self.config) objects = [] self.assertTrue(node.bind("foo", None, None, objects)) @@ -66,7 +68,7 @@ def test_bind_nillable_bytes_content(self): def test_bind_mixed_with_tail_content(self): self.meta.mixed_content = True var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,)) - node = PrimitiveNode(self.meta, var, {}) + node = PrimitiveNode(self.meta, var, {}, self.config) objects = [] self.assertTrue(node.bind("foo", "13", "tail", objects)) @@ -76,7 +78,7 @@ def test_bind_mixed_with_tail_content(self): def test_bind_mixed_without_tail_content(self): self.meta.mixed_content = True var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,)) - node = PrimitiveNode(self.meta, var, {}) + node = PrimitiveNode(self.meta, var, {}, self.config) objects = [] self.assertTrue(node.bind("foo", "13", "", objects)) @@ -84,7 +86,7 @@ def test_bind_mixed_without_tail_content(self): def test_child(self): var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") - node = PrimitiveNode(self.meta, var, {}) + node = PrimitiveNode(self.meta, var, {}, self.config) with self.assertRaises(XmlContextError): node.child("foo", {}, {}, 0) diff --git a/tests/formats/dataclass/parsers/nodes/test_standard.py b/tests/formats/dataclass/parsers/nodes/test_standard.py index 4f00d3b90..d63fe1c4d 100644 --- a/tests/formats/dataclass/parsers/nodes/test_standard.py +++ b/tests/formats/dataclass/parsers/nodes/test_standard.py @@ -3,6 +3,7 @@ from tests.fixtures.artists import Artist from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.generics import DerivedElement +from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.nodes import StandardNode from xsdata.models.enums import DataType from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory @@ -13,10 +14,13 @@ def setUp(self): super().setUp() self.meta = XmlMetaFactory.create(clazz=Artist) self.var = XmlVarFactory.create() + self.config = ParserConfig() def test_bind_simple(self): datatype = DataType.INT - node = StandardNode(self.meta, self.var, datatype, {}, False, False) + node = StandardNode( + self.meta, self.var, datatype, {}, self.config, False, False + ) objects = [] self.assertTrue(node.bind("a", "13", None, objects)) @@ -24,7 +28,9 @@ def test_bind_simple(self): def test_bind_derived(self): datatype = DataType.INT - node = StandardNode(self.meta, self.var, datatype, {}, False, DerivedElement) + node = StandardNode( + self.meta, self.var, datatype, {}, self.config, False, DerivedElement + ) objects = [] self.assertTrue(node.bind("a", "13", None, objects)) @@ -32,7 +38,9 @@ def test_bind_derived(self): def test_bind_wrapper_type(self): datatype = DataType.HEX_BINARY - node = StandardNode(self.meta, self.var, datatype, {}, False, DerivedElement) + node = StandardNode( + self.meta, self.var, datatype, {}, self.config, False, DerivedElement + ) objects = [] self.assertTrue(node.bind("a", "13", None, objects)) @@ -40,7 +48,7 @@ def test_bind_wrapper_type(self): def test_bind_nillable(self): datatype = DataType.STRING - node = StandardNode(self.meta, self.var, datatype, {}, True, None) + node = StandardNode(self.meta, self.var, datatype, {}, self.config, True, None) objects = [] self.assertTrue(node.bind("a", None, None, objects)) @@ -52,7 +60,9 @@ def test_bind_nillable(self): def test_child(self): datatype = DataType.STRING - node = StandardNode(self.meta, self.var, datatype, {}, False, False) + node = StandardNode( + self.meta, self.var, datatype, {}, self.config, False, False + ) with self.assertRaises(XmlContextError): node.child("foo", {}, {}, 0) diff --git a/tests/formats/dataclass/parsers/test_node.py b/tests/formats/dataclass/parsers/test_node.py index 4497a70c7..4bfef17ab 100644 --- a/tests/formats/dataclass/parsers/test_node.py +++ b/tests/formats/dataclass/parsers/test_node.py @@ -182,7 +182,7 @@ def test_end(self, mock_assemble): objects = [("q", "result")] queue = [] var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") - queue.append(PrimitiveNode(var, {}, False)) + queue.append(PrimitiveNode(var, {}, False, parser.config)) self.assertTrue(parser.end(queue, objects, "author", "foobar", None)) self.assertEqual(0, len(queue)) diff --git a/tests/formats/dataclass/parsers/test_utils.py b/tests/formats/dataclass/parsers/test_utils.py index 81871bdb5..618444891 100644 --- a/tests/formats/dataclass/parsers/test_utils.py +++ b/tests/formats/dataclass/parsers/test_utils.py @@ -5,6 +5,7 @@ from xsdata.exceptions import ParserError from xsdata.formats.converter import ConverterFactory from xsdata.formats.dataclass.context import XmlContext +from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.models.enums import Namespace, QNames from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory @@ -116,17 +117,23 @@ def test_validate_fixed_value(self): var = XmlVarFactory.create("fixed", default=lambda: float("nan")) ParserUtils.validate_fixed_value(meta, var, float("nan")) - def test_parse_var_with_warnings(self): + def test_parse_var_with_error(self): meta = XmlMetaFactory.create(clazz=TypeA, qname="foo") var = XmlVarFactory.create("fixed", default="a") + config = ParserConfig() with warnings.catch_warnings(record=True) as w: - result = ParserUtils.parse_var(meta, var, "a", types=[int, float]) + result = ParserUtils.parse_var(meta, var, config, "a", types=[int, float]) expected = ( "Failed to convert value for `TypeA.fixed`\n" " `a` is not a valid `int | float`" ) self.assertEqual("a", result) - self.assertEqual(expected, str(w[-1].message)) + + config.fail_on_converter_warnings = True + with self.assertRaises(ParserError) as cm: + ParserUtils.parse_var(meta, var, config, "a", types=[int, float]) + + self.assertEqual(expected, str(cm.exception)) diff --git a/tests/formats/dataclass/parsers/test_xml.py b/tests/formats/dataclass/parsers/test_xml.py index 6667569e4..5e37f56f4 100644 --- a/tests/formats/dataclass/parsers/test_xml.py +++ b/tests/formats/dataclass/parsers/test_xml.py @@ -33,7 +33,7 @@ def test_end(self, mock_emit_event): queue = [] meta = XmlMetaFactory.create(clazz=Artist) var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(bool,)) - queue.append(PrimitiveNode(meta, var, {})) + queue.append(PrimitiveNode(meta, var, {}, self.parser.config)) result = self.parser.end(queue, objects, "enabled", "true", None) self.assertTrue(result) diff --git a/xsdata/formats/dataclass/parsers/bases.py b/xsdata/formats/dataclass/parsers/bases.py index 97205dc08..9bd4cd68c 100644 --- a/xsdata/formats/dataclass/parsers/bases.py +++ b/xsdata/formats/dataclass/parsers/bases.py @@ -54,15 +54,11 @@ def parse( """ handler = self.handler(clazz=clazz, parser=self) - with warnings.catch_warnings(): - if self.config.fail_on_converter_warnings: - warnings.filterwarnings("error", category=ConverterWarning) - - try: - ns_map = self.ns_map if ns_map is None else ns_map - result = handler.parse(source, ns_map) - except (ConverterWarning, SyntaxError) as e: - raise ParserError(e) + try: + ns_map = self.ns_map if ns_map is None else ns_map + result = handler.parse(source, ns_map) + except SyntaxError as e: + raise ParserError(e) if result is not None: return result diff --git a/xsdata/formats/dataclass/parsers/dict.py b/xsdata/formats/dataclass/parsers/dict.py index d225c90fa..5b167fd3f 100644 --- a/xsdata/formats/dataclass/parsers/dict.py +++ b/xsdata/formats/dataclass/parsers/dict.py @@ -1,10 +1,10 @@ -import warnings -from dataclasses import dataclass, field +from contextlib import suppress +from dataclasses import dataclass, field, replace from typing import Any, Dict, Iterable, List, Optional, Type, Union from typing_extensions import get_args, get_origin -from xsdata.exceptions import ConverterWarning, ParserError +from xsdata.exceptions import ParserError from xsdata.formats.converter import converter from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar @@ -41,18 +41,10 @@ def decode(self, data: Union[List, Dict], clazz: Optional[Type[T]] = None) -> T: An instance of the specified class representing the decoded content. """ tp = self.verify_type(clazz, data) + if not isinstance(data, list): + return self.bind_dataclass(data, tp) - with warnings.catch_warnings(): - if self.config.fail_on_converter_warnings: - warnings.filterwarnings("error", category=ConverterWarning) - - try: - if not isinstance(data, list): - return self.bind_dataclass(data, tp) - - return [self.bind_dataclass(obj, tp) for obj in data] # type: ignore - except ConverterWarning as e: - raise ParserError(e) + return [self.bind_dataclass(obj, tp) for obj in data] # type: ignore def verify_type(self, clazz: Optional[Type[T]], data: Union[Dict, List]) -> Type[T]: """Verify the given data matches the given clazz. @@ -206,12 +198,18 @@ def bind_best_dataclass(self, data: Dict, classes: Iterable[Type[T]]) -> T: obj = None keys = set(data.keys()) max_score = -1.0 + config = replace(self.config, fail_on_converter_warnings=True) + decoder = DictDecoder(config=config, context=self.context) + for clazz in classes: if not self.context.class_type.is_model(clazz): continue if self.context.local_names_match(keys, clazz): - candidate = self.bind_optional_dataclass(data, clazz) + candidate = None + with suppress(Exception): + candidate = decoder.bind_dataclass(data, clazz) + score = self.context.class_type.score_object(candidate) if score > max_score: max_score = score @@ -225,28 +223,6 @@ def bind_best_dataclass(self, data: Dict, classes: Iterable[Type[T]]) -> T: f"to any of the {[cls.__qualname__ for cls in classes]}" ) - def bind_optional_dataclass(self, data: Dict, clazz: Type[T]) -> Optional[T]: - """Bind the input data to the given class type. - - This is a strict process, if there is any warning the process - returns None. This method is used to test if te data fit into - the class type. - - Args: - data: The derived element dictionary - clazz: The target class type to bind the input data - - Returns: - An instance of the class type representing the parsed content - or None if there is any warning or error. - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings("error", category=ConverterWarning) - return self.bind_dataclass(data, clazz) - except Exception: - return None - def bind_value( self, meta: XmlMeta, @@ -328,6 +304,7 @@ def bind_text(self, meta: XmlMeta, var: XmlVar, value: Any) -> Any: return ParserUtils.parse_var( meta=meta, var=var, + config=self.config, value=value, ns_map=EMPTY_MAP, ) diff --git a/xsdata/formats/dataclass/parsers/nodes/element.py b/xsdata/formats/dataclass/parsers/nodes/element.py index c0053a0cb..1a2808086 100644 --- a/xsdata/formats/dataclass/parsers/nodes/element.py +++ b/xsdata/formats/dataclass/parsers/nodes/element.py @@ -196,6 +196,7 @@ def bind_attr(self, params: Dict, var: XmlVar, value: Any): value = ParserUtils.parse_var( meta=self.meta, var=var, + config=self.config, value=value, ns_map=self.ns_map, ) @@ -372,6 +373,7 @@ def bind_text(self, params: Dict, text: Optional[str]) -> bool: value = ParserUtils.parse_var( meta=self.meta, var=var, + config=self.config, value=text, ns_map=self.ns_map, ) @@ -518,7 +520,7 @@ def build_node( ) if not var.any_type and not var.is_wildcard: - return nodes.PrimitiveNode(self.meta, var, ns_map) + return nodes.PrimitiveNode(self.meta, var, ns_map, self.config) datatype = DataType.from_qname(xsi_type) if xsi_type else None derived = var.is_wildcard @@ -528,6 +530,7 @@ def build_node( var, datatype, ns_map, + self.config, var.nillable, derived_factory if derived else None, ) diff --git a/xsdata/formats/dataclass/parsers/nodes/primitive.py b/xsdata/formats/dataclass/parsers/nodes/primitive.py index 8537eb77a..f915d416a 100644 --- a/xsdata/formats/dataclass/parsers/nodes/primitive.py +++ b/xsdata/formats/dataclass/parsers/nodes/primitive.py @@ -2,6 +2,7 @@ from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar +from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils @@ -13,14 +14,16 @@ class PrimitiveNode(XmlNode): meta: The parent xml meta instance var: The xml var instance ns_map: The element namespace prefix-URI map + config: The parser config instance """ - __slots__ = "meta", "var", "ns_map" + __slots__ = "meta", "var", "ns_map", "config" - def __init__(self, meta: XmlMeta, var: XmlVar, ns_map: Dict): + def __init__(self, meta: XmlMeta, var: XmlVar, ns_map: Dict, config: ParserConfig): self.meta = meta self.var = var self.ns_map = ns_map + self.config = config def bind( self, @@ -45,7 +48,11 @@ def bind( Whether the binding process was successful or not. """ obj = ParserUtils.parse_var( - meta=self.meta, var=self.var, value=text, ns_map=self.ns_map + meta=self.meta, + var=self.var, + config=self.config, + value=text, + ns_map=self.ns_map, ) if obj is None and not self.var.nillable: diff --git a/xsdata/formats/dataclass/parsers/nodes/standard.py b/xsdata/formats/dataclass/parsers/nodes/standard.py index 297444ab3..d919e0e5d 100644 --- a/xsdata/formats/dataclass/parsers/nodes/standard.py +++ b/xsdata/formats/dataclass/parsers/nodes/standard.py @@ -2,6 +2,7 @@ from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar +from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.models.enums import DataType @@ -11,13 +12,24 @@ class StandardNode(XmlNode): """XmlNode for elements with a standard xsi:type. Args: + meta: The parent xml meta instance + var: The xml var instance datatype: The element standard xsi data type ns_map: The element namespace prefix-URI map + config: The parser config instance nillable: Specifies whether nil content is allowed derived_factory: The derived element factory """ - __slots__ = "meta", "var", "datatype", "ns_map", "nillable", "derived_factory" + __slots__ = ( + "meta", + "var", + "datatype", + "ns_map", + "config", + "nillable", + "derived_factory", + ) def __init__( self, @@ -25,6 +37,7 @@ def __init__( var: XmlVar, datatype: DataType, ns_map: Dict, + config: ParserConfig, nillable: bool, derived_factory: Optional[Type], ): @@ -32,6 +45,7 @@ def __init__( self.var = var self.datatype = datatype self.ns_map = ns_map + self.config = config self.nillable = nillable self.derived_factory = derived_factory @@ -60,6 +74,7 @@ def bind( obj = ParserUtils.parse_var( meta=self.meta, var=self.var, + config=self.config, value=text, types=[self.datatype.type], ns_map=self.ns_map, diff --git a/xsdata/formats/dataclass/parsers/nodes/union.py b/xsdata/formats/dataclass/parsers/nodes/union.py index fa842df9e..68377336b 100644 --- a/xsdata/formats/dataclass/parsers/nodes/union.py +++ b/xsdata/formats/dataclass/parsers/nodes/union.py @@ -1,15 +1,15 @@ import copy -import warnings -from typing import Any, Dict, List, Optional, Tuple, Type +from contextlib import suppress +from dataclasses import replace +from typing import Any, Dict, List, Optional, Tuple -from xsdata.exceptions import ConverterWarning, ParserError +from xsdata.exceptions import ParserError from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers.bases import NodeParser from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import EventsHandler, XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils -from xsdata.formats.types import T from xsdata.utils.namespaces import target_uri @@ -118,12 +118,26 @@ def bind( obj = None max_score = -1.0 parent_namespace = target_uri(qname) + config = replace(self.config, fail_on_converter_warnings=True) + for clazz in self.var.types: - if self.context.class_type.is_model(clazz): - self.context.build(clazz, parent_ns=parent_namespace) - candidate = self.parse_class(clazz) - else: - candidate = self.parse_value(text, [clazz]) + candidate = None + with suppress(Exception): + if self.context.class_type.is_model(clazz): + self.context.build(clazz, parent_ns=parent_namespace) + parser = NodeParser( + config=config, context=self.context, handler=EventsHandler + ) + candidate = parser.parse(self.events, clazz) + else: + candidate = ParserUtils.parse_var( + meta=self.meta, + var=self.var, + config=config, + value=text, + types=[clazz], + ns_map=self.ns_map, + ) score = self.context.class_type.score_object(candidate) if score > max_score: @@ -136,48 +150,3 @@ def bind( return True raise ParserError(f"Failed to parse union node: {self.var.qname}") - - def parse_class(self, clazz: Type[T]) -> Optional[T]: - """Replay the recorded events and attempt to build the target class. - - Args: - clazz: The target class - - Returns: - The target class instance or None if the recorded - xml events didn't fit the class. - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings("error", category=ConverterWarning) - - parser = NodeParser( - config=self.config, context=self.context, handler=EventsHandler - ) - return parser.parse(self.events, clazz) - except Exception: - return None - - def parse_value(self, value: Any, types: List[Type]) -> Any: - """Parse simple values. - - Args: - value: The xml value - types: The list of the candidate simple types - - Returns: - The parsed value or None if value didn't match - with any of the given types. - """ - try: - with warnings.catch_warnings(): - warnings.filterwarnings("error", category=ConverterWarning) - return ParserUtils.parse_var( - meta=self.meta, - var=self.var, - value=value, - types=types, - ns_map=self.ns_map, - ) - except Exception: - return None diff --git a/xsdata/formats/dataclass/parsers/utils.py b/xsdata/formats/dataclass/parsers/utils.py index 2d222609e..1a7742100 100644 --- a/xsdata/formats/dataclass/parsers/utils.py +++ b/xsdata/formats/dataclass/parsers/utils.py @@ -1,11 +1,12 @@ import math import warnings from collections import UserList -from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Type, Union +from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Type from xsdata.exceptions import ConverterError, ConverterWarning, ParserError from xsdata.formats.converter import QNameConverter, converter from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar +from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.models.enums import QNames from xsdata.utils import collections, constants, text from xsdata.utils.namespaces import build_qname @@ -83,6 +84,7 @@ def parse_var( cls, meta: XmlMeta, var: XmlVar, + config: ParserConfig, value: Any, ns_map: Optional[Dict] = None, default: Any = None, @@ -95,6 +97,7 @@ def parse_var( Args: meta: The xml meta instance var: The xml var instance + config: The parser config instance value: A primitive value or a list of primitive values ns_map: The element namespace prefix-URI map default: Override the var default value @@ -115,11 +118,11 @@ def parse_var( format=format or var.format, ) except ConverterError as ex: - message = f" {ex}" - warnings.warn( - f"Failed to convert value for `{meta.clazz.__qualname__}.{var.name}`\n{message}", - ConverterWarning, - ) + message = f"Failed to convert value for `{meta.clazz.__qualname__}.{var.name}`\n {ex}" + if config.fail_on_converter_warnings: + raise ParserError(message) + + warnings.warn(message, ConverterWarning) return value