Skip to content

Commit 9ffa4d0

Browse files
authored
Extract ErrorsList base errors list manager (#105)
* Extract ErrorsList base errors list manager * Remove duplicate InvalidItemException
1 parent f94d8dd commit 9ffa4d0

File tree

3 files changed

+75
-97
lines changed

3 files changed

+75
-97
lines changed

aws_doc_sdk_examples_tools/entities.py

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
from typing import List, Optional, TypeVar, Dict, Set, Union, Tuple, Iterator, Iterable
1+
from typing import Optional, Dict, Set, Tuple
22
from dataclasses import dataclass
33
import re
44

5-
K = TypeVar("K")
6-
7-
8-
class InvalidItemException(Exception):
9-
def __init__(self, item: K):
10-
super().__init__(self, f"Cannot append {item!r} to EntityErrors")
5+
from .metadata_errors import ErrorsList
116

127

138
@dataclass
@@ -28,39 +23,8 @@ def message(self):
2823
return f"{self.entity} not found."
2924

3025

31-
class EntityErrors:
32-
def __init__(self):
33-
self._errors: List[EntityError] = []
34-
35-
def append(self, maybe_error: Union[K, EntityError]):
36-
if not isinstance(maybe_error, EntityError):
37-
raise InvalidItemException(maybe_error)
38-
self._errors.append(maybe_error)
39-
40-
def extend(self, errors: Iterable[EntityError]):
41-
self._errors.extend(errors)
42-
43-
def __getitem__(self, key: int) -> EntityError:
44-
return self._errors[key]
45-
46-
def __setitem__(self, key: int, value: EntityError):
47-
self._errors[key] = value
48-
49-
def __len__(self) -> int:
50-
return len(self._errors)
51-
52-
def __iter__(self) -> Iterator[EntityError]:
53-
return self._errors.__iter__()
54-
55-
def __repr__(self) -> str:
56-
return repr(self._errors)
57-
58-
def __str__(self) -> str:
59-
errs = "\n".join([f"\t{err}" for err in self._errors])
60-
return f"EntityErrors with {len(self)} errors:\n{errs}"
61-
62-
def __eq__(self, __value: object) -> bool:
63-
return isinstance(__value, EntityErrors) and self._errors == __value._errors
26+
class EntityErrors(ErrorsList[EntityError]):
27+
pass
6428

6529

6630
def expand_all_entities(

aws_doc_sdk_examples_tools/entities_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import pytest
22
from .entities import (
33
EntityErrors,
4-
InvalidItemException,
54
expand_all_entities,
65
MissingEntityError,
76
)
7+
from .metadata_errors import InvalidItemException
88

99

1010
def test_entity_errors_append():
@@ -14,7 +14,8 @@ def test_entity_errors_append():
1414
assert errors._errors[0].entity == "entity1"
1515

1616
with pytest.raises(InvalidItemException):
17-
errors.append("invalid item")
17+
# test runtime type checking
18+
errors.append("invalid item") # type: ignore
1819

1920

2021
def test_expand_missing_entities():

aws_doc_sdk_examples_tools/metadata_errors.py

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,33 @@
66
import re
77
from dataclasses import dataclass, field
88
from pathlib import Path
9-
from typing import Optional, Iterator, Iterable, List, TypeVar
9+
from typing import Optional, Iterator, Iterable, List, TypeVar, Generic
1010

1111

12-
@dataclass
13-
class MetadataError:
14-
file: Optional[Path] = None
15-
id: Optional[str] = None
16-
17-
def prefix(self):
18-
prefix = f"In {self.file or 'several'} at {self.id},"
19-
return prefix
20-
21-
def message(self) -> str:
22-
return ""
23-
24-
def __str__(self):
25-
return f"{self.prefix()} {self.message()}"
12+
ErrorT = TypeVar("ErrorT")
2613

2714

28-
@dataclass
29-
class MetadataParseError(MetadataError):
30-
id: Optional[str] = None
31-
language: Optional[str] = None
32-
sdk_version: Optional[int] = None
33-
34-
def prefix(self):
35-
prefix = super().prefix() + f" example {self.id}"
36-
if self.language:
37-
prefix += f" {self.language}"
38-
if self.sdk_version:
39-
prefix += f":{self.sdk_version}"
40-
return prefix
41-
42-
def __str__(self):
43-
return f"{self.prefix()} {self.message()}"
44-
45-
46-
K = TypeVar("K")
47-
48-
49-
class InvalidItemException(Exception):
50-
def __init__(self, item: MetadataParseError):
15+
class InvalidItemException(Exception, Generic[ErrorT]):
16+
def __init__(self, item: ErrorT):
5117
super().__init__(self, f"Cannot append {item!r} to ExampleErrors")
5218

5319

54-
class DuplicateItemException(Exception):
55-
def __init__(self, item: MetadataError):
56-
super().__init__(self, f"Already have item {item!r} in ExampleErrors")
57-
58-
59-
class MetadataErrors:
20+
class ErrorsList(Generic[ErrorT]):
6021
"""MyPy isn't catching List[Foo].append(List[Foo])"""
6122

6223
def __init__(self, no_duplicates: bool = False):
6324
self.no_duplicates = no_duplicates
64-
self._errors: List[MetadataError] = []
65-
66-
def append(self, item: MetadataError):
67-
if not isinstance(item, MetadataError):
25+
self._errors: List[ErrorT] = []
26+
27+
def append(self, item: ErrorT):
28+
# Look up the generic type. This is reliant on the internal implementation
29+
# of __orig_bases__, but it will definitely fail tests if a python minor
30+
# version breaks it.
31+
generic = self.__orig_bases__[0] # type: ignore
32+
T = generic.__args__[0]
33+
if not isinstance(item, T):
6834
raise InvalidItemException(item)
35+
6936
"""
7037
It is dangerous to go alone: 🗡️
7138
If you're seeing duplicated Errors, and aren't sure why, uncommenting these lines may help you debug it.
@@ -74,25 +41,25 @@ def append(self, item: MetadataError):
7441
# raise DuplicateItemException(item)
7542
self._errors.append(item)
7643

77-
def extend(self, errors: Iterable[MetadataError]):
44+
def extend(self, errors: Iterable[ErrorT]):
7845
self._errors.extend(errors)
7946

80-
def maybe_extend(self, maybe_errors: K | MetadataErrors) -> K | None:
81-
if isinstance(maybe_errors, MetadataErrors):
47+
def maybe_extend(self, maybe_errors: K | ErrorsList[ErrorT]) -> K | None:
48+
if isinstance(maybe_errors, ErrorsList):
8249
self.extend(maybe_errors._errors)
8350
return None
8451
return maybe_errors
8552

86-
def __getitem__(self, key: int) -> MetadataError:
53+
def __getitem__(self, key: int) -> ErrorT:
8754
return self._errors[key]
8855

89-
def __setitem__(self, key: int, value: MetadataError):
56+
def __setitem__(self, key: int, value: ErrorT):
9057
self._errors[key] = value
9158

9259
def __len__(self) -> int:
9360
return len(self._errors)
9461

95-
def __iter__(self) -> Iterator[MetadataError]:
62+
def __iter__(self) -> Iterator[ErrorT]:
9663
return self._errors.__iter__()
9764

9865
def __repr__(self) -> str:
@@ -103,7 +70,53 @@ def __str__(self) -> str:
10370
return f"ExampleErrors with {len(self)} errors:\n{errs}"
10471

10572
def __eq__(self, __value: object) -> bool:
106-
return isinstance(__value, MetadataErrors) and self._errors == __value._errors
73+
return isinstance(__value, ErrorsList) and self._errors == __value._errors
74+
75+
76+
@dataclass
77+
class MetadataError:
78+
file: Optional[Path] = None
79+
id: Optional[str] = None
80+
81+
def prefix(self):
82+
prefix = f"In {self.file or 'several'} at {self.id},"
83+
return prefix
84+
85+
def message(self) -> str:
86+
return ""
87+
88+
def __str__(self):
89+
return f"{self.prefix()} {self.message()}"
90+
91+
92+
class MetadataErrors(ErrorsList[MetadataError]):
93+
pass
94+
95+
96+
@dataclass
97+
class MetadataParseError(MetadataError):
98+
id: Optional[str] = None
99+
language: Optional[str] = None
100+
sdk_version: Optional[int] = None
101+
102+
def prefix(self):
103+
prefix = super().prefix() + f" example {self.id}"
104+
if self.language:
105+
prefix += f" {self.language}"
106+
if self.sdk_version:
107+
prefix += f":{self.sdk_version}"
108+
return prefix
109+
110+
def __str__(self):
111+
return f"{self.prefix()} {self.message()}"
112+
113+
114+
K = TypeVar("K")
115+
116+
117+
class DuplicateItemException(Exception):
118+
def __init__(self, item: MetadataError):
119+
super().__init__(self, f"Already have item {item!r} in ExampleErrors")
107120

108121

109122
@dataclass

0 commit comments

Comments
 (0)