Skip to content

Commit

Permalink
🎨 [refactor] Updated Error infrastructure (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrownell authored Aug 14, 2024
2 parents b85efd8 + 38423ef commit 413e83b
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 103 deletions.
84 changes: 39 additions & 45 deletions src/SimpleSchemaGenerator/Common/Error.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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),
)


Expand All @@ -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__(
Expand Down
11 changes: 7 additions & 4 deletions src/SimpleSchemaGenerator/Schema/Elements/Common/Cardinality.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from .Element import Element
from ..Expressions.IntegerExpression import IntegerExpression

from .... import Errors


Expand Down Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions src/SimpleSchemaGenerator/Schema/Elements/Common/Metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)

# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
)

# ----------------------------------------------------------------------
Expand Down Expand Up @@ -98,19 +101,25 @@ 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 [
ParseIncludeStatementType.Module,
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)

# ----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)

# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)

# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 413e83b

Please sign in to comment.