From 38423ef3b26c8235dad5e01722221e65dc220b8a Mon Sep 17 00:00:00 2001 From: David Brownell Date: Wed, 14 Aug 2024 12:48:18 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20[refactor]=20Updated=20Error=20i?= =?UTF-8?q?nfrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SimpleSchemaGenerator/Common/Error.py | 84 +++++++++---------- .../Schema/Elements/Common/Cardinality.py | 11 ++- .../Schema/Elements/Common/Metadata.py | 10 ++- .../Elements/Expressions/TupleExpression.py | 4 +- .../Elements/Statements/ExtensionStatement.py | 10 ++- .../Elements/Statements/RootStatement.py | 4 +- .../Elements/Common/ParseIdentifier.py | 8 +- .../Statements/ParseIncludeStatement.py | 29 ++++--- .../Elements/Types/ParseIdentifierType.py | 8 +- .../Grammar/Elements/Types/ParseTupleType.py | 4 +- .../Elements/Types/ParseVariantType.py | 8 +- .../Schema/Parse/ANTLR/Parse.py | 37 ++++---- tests/Common/Error_UnitTest.py | 41 ++++++--- 13 files changed, 155 insertions(+), 103 deletions(-) diff --git a/src/SimpleSchemaGenerator/Common/Error.py b/src/SimpleSchemaGenerator/Common/Error.py index 29c6655..da76454 100644 --- a/src/SimpleSchemaGenerator/Common/Error.py +++ b/src/SimpleSchemaGenerator/Common/Error.py @@ -19,10 +19,10 @@ from dataclasses import dataclass, field, InitVar, make_dataclass from enum import Enum -from functools import singledispatch +from functools import singledispatch, singledispatchmethod from io import StringIO from pathlib import Path -from typing import Type as PythonType +from typing import Optional, Type as PythonType from dbrownell_Common import TextwrapEx # type: ignore[import-untyped] @@ -60,49 +60,21 @@ def __post_init__( object.__setattr__(self, "regions", regions) # ---------------------------------------------------------------------- + @singledispatchmethod @classmethod def Create(cls, *args, **kwargs) -> "Error": return cls(*args, **kwargs) # ---------------------------------------------------------------------- + @Create.register @classmethod - def CreateAsException(cls, *args, **kwargs) -> "SimpleSchemaGeneratorException": - error = cls(*args, **kwargs) - return SimpleSchemaGeneratorException(error) - - # ---------------------------------------------------------------------- - def __str__(self) -> str: - if len(self.regions) == 1 and "\n" not in self.message: - return "{} ({})".format(self.message, self.regions[0]) - - return textwrap.dedent( - """\ - {} - - {} - """, - ).format( - self.message.rstrip(), - "\n".join(" - {}".format(region) for region in self.regions), - ) - - -# ---------------------------------------------------------------------- -@dataclass(frozen=True) -class ExceptionError(Error): - """Errors based on a python Exception.""" - - # ---------------------------------------------------------------------- - ex: Exception - - # ---------------------------------------------------------------------- - @classmethod - def Create( # pylint: disable=arguments-differ + def _( cls, ex: Exception, + region: Optional[Region] = None, *, include_callstack: bool = True, - ) -> "ExceptionError": + ): # -> "Error": regions: list[Region] = [] if include_callstack: @@ -132,21 +104,43 @@ def Create( # pylint: disable=arguments-differ ), ) + assert regions, sink_str + regions.reverse() + if region is not None: + regions.insert(0, region) + header = "Python Exception: " - return ExceptionError( - "{}{}".format( - header, - TextwrapEx.Indent( - str(ex), - len(header), - skip_first_line=True, - ), + instance = cls( + header + + TextwrapEx.Indent( + str(ex), + len(header), + skip_first_line=True, ), regions, - ex, + ) + + object.__setattr__(instance, "ex", ex) + + return instance + + # ---------------------------------------------------------------------- + def __str__(self) -> str: + if len(self.regions) == 1 and "\n" not in self.message: + return "{} ({})".format(self.message, self.regions[0]) + + return textwrap.dedent( + """\ + {} + + {} + """, + ).format( + self.message.rstrip(), + "\n".join(" - {}".format(region) for region in self.regions), ) @@ -157,7 +151,7 @@ class SimpleSchemaGeneratorException(Exception): # ---------------------------------------------------------------------- error: InitVar[Error] - errors: list[Error] = field(init=False) + errors: list[Error] = field(init=False) # TODO: Will we ever initialize with multiple errors? # ---------------------------------------------------------------------- def __post_init__( diff --git a/src/SimpleSchemaGenerator/Schema/Elements/Common/Cardinality.py b/src/SimpleSchemaGenerator/Schema/Elements/Common/Cardinality.py index b70b03b..7de81a9 100644 --- a/src/SimpleSchemaGenerator/Schema/Elements/Common/Cardinality.py +++ b/src/SimpleSchemaGenerator/Schema/Elements/Common/Cardinality.py @@ -21,6 +21,7 @@ from .Element import Element from ..Expressions.IntegerExpression import IntegerExpression + from .... import Errors @@ -53,10 +54,12 @@ def __post_init__( assert min_param is not None if max_param is not None and max_param.value < min_param.value: - raise Errors.CardinalityInvalidRange.CreateAsException( - max_param.region, - min_param.value, - max_param.value, + raise Errors.SimpleSchemaGeneratorException( + Errors.CardinalityInvalidRange.Create( + max_param.region, + min_param.value, + max_param.value, + ), ) # Commit diff --git a/src/SimpleSchemaGenerator/Schema/Elements/Common/Metadata.py b/src/SimpleSchemaGenerator/Schema/Elements/Common/Metadata.py index 5da4b47..4e86705 100644 --- a/src/SimpleSchemaGenerator/Schema/Elements/Common/Metadata.py +++ b/src/SimpleSchemaGenerator/Schema/Elements/Common/Metadata.py @@ -62,10 +62,12 @@ def __post_init__( prev_value = items.get(key, None) if prev_value is not None: - raise Errors.MetadataItemDuplicated.CreateAsException( - item.name.region, - key, - prev_value.name.region, + raise Errors.SimpleSchemaGeneratorException( + Errors.MetadataItemDuplicated.Create( + item.name.region, + key, + prev_value.name.region, + ), ) items[key] = item diff --git a/src/SimpleSchemaGenerator/Schema/Elements/Expressions/TupleExpression.py b/src/SimpleSchemaGenerator/Schema/Elements/Expressions/TupleExpression.py index cf267fe..c2e7c8f 100644 --- a/src/SimpleSchemaGenerator/Schema/Elements/Expressions/TupleExpression.py +++ b/src/SimpleSchemaGenerator/Schema/Elements/Expressions/TupleExpression.py @@ -36,7 +36,9 @@ class TupleExpression(Expression): # ---------------------------------------------------------------------- def __post_init__(self): if not self.value: - raise Errors.TupleExpressionEmpty.CreateAsException(self.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.TupleExpressionEmpty.Create(self.region) + ) super(TupleExpression, self).__post_init__() diff --git a/src/SimpleSchemaGenerator/Schema/Elements/Statements/ExtensionStatement.py b/src/SimpleSchemaGenerator/Schema/Elements/Statements/ExtensionStatement.py index 266389f..c433d6e 100644 --- a/src/SimpleSchemaGenerator/Schema/Elements/Statements/ExtensionStatement.py +++ b/src/SimpleSchemaGenerator/Schema/Elements/Statements/ExtensionStatement.py @@ -67,10 +67,12 @@ def __post_init__( prev_value = keyword_args.get(key) if prev_value is not None: - raise Errors.ExtensionStatementDuplicateKeywordArgError.CreateAsException( - keyword_arg.name.region, - key, - prev_value.name.region, + raise Errors.SimpleSchemaGeneratorException( + Errors.ExtensionStatementDuplicateKeywordArgError.Create( + keyword_arg.name.region, + key, + prev_value.name.region, + ), ) keyword_args[key] = keyword_arg diff --git a/src/SimpleSchemaGenerator/Schema/Elements/Statements/RootStatement.py b/src/SimpleSchemaGenerator/Schema/Elements/Statements/RootStatement.py index 055a952..2348aea 100644 --- a/src/SimpleSchemaGenerator/Schema/Elements/Statements/RootStatement.py +++ b/src/SimpleSchemaGenerator/Schema/Elements/Statements/RootStatement.py @@ -34,7 +34,9 @@ class RootStatement(Statement): def __post_init__(self): for statement in self.statements: if isinstance(statement, RootStatement): - raise Errors.RootStatementInvalidNested.CreateAsException(statement.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.RootStatementInvalidNested.Create(statement.region) + ) # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- diff --git a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Common/ParseIdentifier.py b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Common/ParseIdentifier.py index ac16084..e454b6a 100644 --- a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Common/ParseIdentifier.py +++ b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Common/ParseIdentifier.py @@ -42,12 +42,16 @@ def __post_init__(self): first_char = self.__class__._GetFirstChar(self.value) # pylint: disable=protected-access if first_char is None: - raise Errors.ParseIdentifierNoChars.CreateAsException(self.region, self.value) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIdentifierNoChars.Create(self.region, self.value) + ) if not ( ("a" <= first_char <= "z") or ("A" <= first_char <= "Z") or emoji.is_emoji(first_char) ): - raise Errors.ParseIdentifierNotAlpha.CreateAsException(self.region, self.value) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIdentifierNotAlpha.Create(self.region, self.value) + ) # Commit object.__setattr__(self, "_first_char", first_char) diff --git a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Statements/ParseIncludeStatement.py b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Statements/ParseIncludeStatement.py index 5ab65ed..5244f80 100644 --- a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Statements/ParseIncludeStatement.py +++ b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Statements/ParseIncludeStatement.py @@ -55,15 +55,18 @@ class ParseIncludeStatementItem(Element): # ---------------------------------------------------------------------- def __post_init__(self): if not self.element_name.is_type: - raise Errors.ParseIncludeStatementItemNotType.CreateAsException( - self.element_name.region, - self.element_name.value, + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIncludeStatementItemNotType.Create( + self.element_name.region, self.element_name.value + ) ) if not self.reference_name.is_type: - raise Errors.ParseIncludeStatementItemReferenceNotType.CreateAsException( - self.reference_name.region, - self.reference_name.value, + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIncludeStatementItemReferenceNotType.Create( + self.reference_name.region, + self.reference_name.value, + ) ) # ---------------------------------------------------------------------- @@ -98,8 +101,10 @@ class ParseIncludeStatement(Statement): # ---------------------------------------------------------------------- def __post_init__(self): if not self.filename.value.is_file(): - raise Errors.ParseIncludeStatementInvalidFile.CreateAsException( - self.filename.region, self.filename.value + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIncludeStatementInvalidFile.Create( + self.filename.region, self.filename.value + ) ) if self.include_type in [ @@ -107,10 +112,14 @@ def __post_init__(self): ParseIncludeStatementType.Star, ]: if self.items: - raise Errors.ParseIncludeStatementInvalidItems.CreateAsException(self.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIncludeStatementInvalidItems.Create(self.region) + ) elif self.include_type == ParseIncludeStatementType.Package: if not self.items: - raise Errors.ParseIncludeStatementMissingItems.CreateAsException(self.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIncludeStatementMissingItems.Create(self.region) + ) else: assert False, self.include_type # pragma: no cover diff --git a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseIdentifierType.py b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseIdentifierType.py index 67ec93e..2a9643a 100644 --- a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseIdentifierType.py +++ b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseIdentifierType.py @@ -37,12 +37,14 @@ class ParseIdentifierType(ParseType): # ---------------------------------------------------------------------- def __post_init__(self): if not self.identifiers: - raise Errors.ParseIdentifierTypeEmpty.CreateAsException(self.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIdentifierTypeEmpty.Create(self.region) + ) for identifier in self.identifiers: if not identifier.is_type: - raise Errors.ParseIdentifierTypeNotType.CreateAsException( - identifier.region, identifier.value + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseIdentifierTypeNotType.Create(identifier.region, identifier.value) ) # ---------------------------------------------------------------------- diff --git a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseTupleType.py b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseTupleType.py index e7622ac..d3e0664 100644 --- a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseTupleType.py +++ b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseTupleType.py @@ -34,7 +34,9 @@ class ParseTupleType(ParseType): # ---------------------------------------------------------------------- def __post_init__(self): if not self.types: - raise Errors.ParseTupleTypeMissingTypes.CreateAsException(self.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseTupleTypeMissingTypes.Create(self.region) + ) # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- diff --git a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseVariantType.py b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseVariantType.py index 389df43..b551e35 100644 --- a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseVariantType.py +++ b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Grammar/Elements/Types/ParseVariantType.py @@ -34,11 +34,15 @@ class ParseVariantType(ParseType): # ---------------------------------------------------------------------- def __post_init__(self): if len(self.types) < 2: - raise Errors.ParseVariantTypeMissingTypes.CreateAsException(self.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseVariantTypeMissingTypes.Create(self.region) + ) for the_type in self.types: if isinstance(the_type, ParseVariantType): - raise Errors.ParseVariantTypeNestedType.CreateAsException(the_type.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseVariantTypeNestedType.Create(the_type.region) + ) # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- diff --git a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Parse.py b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Parse.py index fa3ec0d..4f6e047 100644 --- a/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Parse.py +++ b/src/SimpleSchemaGenerator/Schema/Parse/ANTLR/Parse.py @@ -980,7 +980,9 @@ def visitParse_structure_statement( if isinstance(child, ParseIdentifierType): bases.append(child) else: - raise Errors.ParseStructureStatementInvalidBase.CreateAsException(child.region) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseStructureStatementInvalidBase.Create(child.region) + ) elif isinstance(child, Cardinality): assert cardinality is None, cardinality @@ -1228,14 +1230,18 @@ def Impl( if root is None: if directory_indicator is not None: - raise Errors.ParseCreateIncludeStatementInvalidDirectory.CreateAsException( - filename_or_directory.region, - filename_or_directory.value, + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseCreateIncludeStatementInvalidDirectory.Create( + filename_or_directory.region, + filename_or_directory.value, + ), ) else: - raise Errors.ParseCreateIncludeStatementInvalidFilename.CreateAsException( - filename_or_directory.region, - filename_or_directory.value, + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseCreateIncludeStatementInvalidFilename.Create( + filename_or_directory.region, + filename_or_directory.value, + ), ) filename: Optional[Path] = None @@ -1244,7 +1250,9 @@ def Impl( if root.is_dir(): if is_star_include: - raise Errors.ParseCreateIncludeStatementDirWithStar.CreateAsException(region, root) + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseCreateIncludeStatementDirWithStar.Create(region, root) + ) filename = ResolveIncludeFilename( root / items[0].element_name.value, @@ -1258,9 +1266,11 @@ def Impl( ) if filename is None: - raise Errors.ParseCreateIncludeStatementInvalidFilename.CreateAsException( - filename_region, - items[0].element_name.value, + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseCreateIncludeStatementInvalidFilename.Create( + filename_region, + items[0].element_name.value, + ), ) include_type = ParseIncludeStatementType.Module @@ -1290,9 +1300,8 @@ def Impl( break if workspace is None: - raise Errors.ParseCreateIncludeStatementInvalidWorkspace.CreateAsException( - region, - filename, + raise Errors.SimpleSchemaGeneratorException( + Errors.ParseCreateIncludeStatementInvalidWorkspace.Create(region, filename) ) # Get the relative path for the workspace diff --git a/tests/Common/Error_UnitTest.py b/tests/Common/Error_UnitTest.py index b575ed5..7a9ac93 100644 --- a/tests/Common/Error_UnitTest.py +++ b/tests/Common/Error_UnitTest.py @@ -112,19 +112,20 @@ def test_MutlipleLinesMultipleRegions(self): """, ) - # ---------------------------------------------------------------------- - def test_CreateAsException(self): - region = Region( - Path("foo"), - Location(1, 2), - Location(3, 4), - ) - ex = Error.CreateAsException("Single line, single_region", region) +# ---------------------------------------------------------------------- +def test_SimpleSchemaGeneratorException(): + region = Region( + Path("foo"), + Location(1, 2), + Location(3, 4), + ) - assert len(ex.errors) == 1 - assert ex.errors[0].message == "Single line, single_region" - assert ex.errors[0].regions == [region] + ex = SimpleSchemaGeneratorException(Error.Create("Single line, single_region", region)) + + assert len(ex.errors) == 1 + assert ex.errors[0].message == "Single line, single_region" + assert ex.errors[0].regions == [region] # ---------------------------------------------------------------------- @@ -134,7 +135,7 @@ def test_ExceptionError(): except Exception as ex: the_ex = ex - ee = ExceptionError.Create(the_ex) + ee = Error.Create(the_ex) assert ee.ex is the_ex assert ee.message == "Python Exception: This is my exception" @@ -142,6 +143,22 @@ def test_ExceptionError(): assert ee.regions[0].filename == Path(__file__) +# ---------------------------------------------------------------------- +def test_ExceptionErrorWithRegion(): + try: + raise Exception("This is my exception") + except Exception as ex: + the_ex = ex + + ee = Error.Create(the_ex, Region.Create(Path("foo"), 1, 2, 3, 4)) + + assert ee.ex is the_ex + assert ee.message == "Python Exception: This is my exception" + assert len(ee.regions) == 2 + assert ee.regions[0] == Region.Create(Path("foo"), 1, 2, 3, 4) + assert ee.regions[1].filename == Path(__file__) + + # ---------------------------------------------------------------------- def test_CreateErrorType(): # ----------------------------------------------------------------------