Skip to content

Commit 325a834

Browse files
authored
Adopt Pydantic 2.0 (#264)
* Bump mypy * Adjust code for pydanticv2 * Remove pydantic comments * add todo for nestedencoder * constraint mypy
1 parent 57ad93d commit 325a834

File tree

11 files changed

+691
-519
lines changed

11 files changed

+691
-519
lines changed

circuit_maintenance_parser/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ def get_provider_class(provider_name: str) -> Type[GenericProvider]:
9595

9696
def get_provider_class_from_sender(email_sender: str) -> Type[GenericProvider]:
9797
"""Returns the notification parser class for an email sender address."""
98-
9998
for provider_parser in SUPPORTED_PROVIDERS:
10099
if provider_parser.get_default_organizer() == email_sender:
101100
break

circuit_maintenance_parser/data.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import List, NamedTuple, Optional, Type, Set
44

55
import email
6-
from pydantic import BaseModel, Extra
6+
from pydantic import BaseModel
77
from circuit_maintenance_parser.constants import EMAIL_HEADER_SUBJECT, EMAIL_HEADER_DATE
88

99

@@ -18,7 +18,7 @@ class DataPart(NamedTuple):
1818
content: bytes
1919

2020

21-
class NotificationData(BaseModel, extra=Extra.forbid):
21+
class NotificationData(BaseModel, extra="forbid"):
2222
"""Base class for Notification Data types."""
2323

2424
data_parts: List[DataPart] = []

circuit_maintenance_parser/output.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from typing import List
1010

11-
from pydantic import BaseModel, validator, StrictStr, StrictInt, Extra, PrivateAttr
11+
from pydantic import field_validator, BaseModel, StrictStr, StrictInt, PrivateAttr
1212

1313

1414
class Impact(str, Enum):
@@ -52,7 +52,7 @@ class Status(str, Enum):
5252
NO_CHANGE = "NO-CHANGE"
5353

5454

55-
class CircuitImpact(BaseModel, extra=Extra.forbid):
55+
class CircuitImpact(BaseModel, extra="forbid"):
5656
"""CircuitImpact class.
5757
5858
Each Circuit Maintenance can contain multiple affected circuits, and each one can have a different level of impact.
@@ -73,23 +73,31 @@ class CircuitImpact(BaseModel, extra=Extra.forbid):
7373
... )
7474
Traceback (most recent call last):
7575
...
76-
pydantic.error_wrappers.ValidationError: 1 validation error for CircuitImpact
76+
pydantic_core._pydantic_core.ValidationError: 1 validation error for CircuitImpact
7777
impact
78-
value is not a valid enumeration member; permitted: 'NO-IMPACT', 'REDUCED-REDUNDANCY', 'DEGRADED', 'OUTAGE' (type=type_error.enum; enum_values=[<Impact.NO_IMPACT: 'NO-IMPACT'>, <Impact.REDUCED_REDUNDANCY: 'REDUCED-REDUNDANCY'>, <Impact.DEGRADED: 'DEGRADED'>, <Impact.OUTAGE: 'OUTAGE'>])
78+
Input should be 'NO-IMPACT', 'REDUCED-REDUNDANCY', 'DEGRADED' or 'OUTAGE' [type=enum, input_value='wrong impact', input_type=str]
7979
"""
8080

8181
circuit_id: StrictStr
8282
# Optional Attributes
8383
impact: Impact = Impact.OUTAGE
8484

8585
# pylint: disable=no-self-argument
86-
@validator("impact")
86+
@field_validator("impact")
87+
@classmethod
8788
def validate_impact_type(cls, value):
8889
"""Validate Impact type."""
8990
if value not in Impact:
9091
raise ValueError("Not a valid impact type")
9192
return value
9293

94+
def to_json(self):
95+
"""Return a JSON serializable dict."""
96+
return {
97+
"circuit_id": self.circuit_id,
98+
"impact": self.impact.value,
99+
}
100+
93101

94102
class Metadata(BaseModel):
95103
"""Metadata class to provide context about the Maintenance object."""
@@ -100,7 +108,7 @@ class Metadata(BaseModel):
100108
generated_by_llm: bool = False
101109

102110

103-
class Maintenance(BaseModel, extra=Extra.forbid):
111+
class Maintenance(BaseModel, extra="forbid"):
104112
"""Maintenance class.
105113
106114
Mandatory attributes:
@@ -164,34 +172,40 @@ class Maintenance(BaseModel, extra=Extra.forbid):
164172

165173
def __init__(self, **data):
166174
"""Initialize the Maintenance object."""
167-
self._metadata = data.pop("_metadata")
175+
metadata = data.pop("_metadata")
168176
super().__init__(**data)
177+
self._metadata = metadata
169178

170-
# pylint: disable=no-self-argument
171-
@validator("status")
179+
@field_validator("status")
180+
@classmethod
172181
def validate_status_type(cls, value):
173182
"""Validate Status type."""
174183
if value not in Status:
175184
raise ValueError("Not a valid status type")
176185
return value
177186

178-
@validator("provider", "account", "maintenance_id", "organizer")
187+
@field_validator("provider", "account", "maintenance_id", "organizer")
188+
@classmethod
179189
def validate_empty_strings(cls, value):
180190
"""Validate emptry strings."""
181191
if value in ["", "None"]:
182192
raise ValueError("String is empty or 'None'")
183193
return value
184194

185-
@validator("circuits")
195+
@field_validator("circuits")
196+
@classmethod
186197
def validate_empty_circuits(cls, value, values):
187198
"""Validate non-cancel notifications have a populated circuit list."""
199+
values = values.data
188200
if len(value) < 1 and str(values["status"]) in ("CANCELLED", "COMPLETED"):
189201
raise ValueError("At least one circuit has to be included in the maintenance")
190202
return value
191203

192-
@validator("end")
204+
@field_validator("end")
205+
@classmethod
193206
def validate_end_time(cls, end, values):
194207
"""Validate that End time happens after Start time."""
208+
values = values.data
195209
if "start" not in values:
196210
raise ValueError("Start time is a mandatory attribute.")
197211
start = values["start"]
@@ -209,6 +223,6 @@ def to_json(self) -> str:
209223
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=2)
210224

211225
@property
212-
def metadata(self):
226+
def metadata(self) -> Metadata:
213227
"""Get Maintenance Metadata."""
214228
return self._metadata

circuit_maintenance_parser/parser.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import bs4 # type: ignore
1313
from bs4.element import ResultSet # type: ignore
1414

15-
from pydantic import BaseModel
15+
from pydantic import BaseModel, PrivateAttr
1616
from icalendar import Calendar # type: ignore
1717

1818
from circuit_maintenance_parser.errors import ParserError
@@ -34,15 +34,15 @@ class Parser(BaseModel):
3434
"""
3535

3636
# _data_types are used to match the Parser to to each type of DataPart
37-
_data_types = ["text/plain", "plain"]
37+
_data_types = PrivateAttr(["text/plain", "plain"])
3838

3939
# TODO: move it to where it is used, Cogent parser
4040
_geolocator = Geolocator()
4141

4242
@classmethod
4343
def get_data_types(cls) -> List[str]:
4444
"""Return the expected data type."""
45-
return cls._data_types
45+
return cls._data_types.get_default()
4646

4747
@classmethod
4848
def get_name(cls) -> str:
@@ -92,7 +92,7 @@ class ICal(Parser):
9292
Reference: https://tools.ietf.org/html/draft-gunter-calext-maintenance-notifications-00
9393
"""
9494

95-
_data_types = ["text/calendar", "ical", "icalendar"]
95+
_data_types = PrivateAttr(["text/calendar", "ical", "icalendar"])
9696

9797
def parser_hook(self, raw: bytes, content_type: str):
9898
"""Execute parsing."""
@@ -164,7 +164,7 @@ def parse_ical(gcal: Calendar) -> List[Dict]:
164164
class Html(Parser):
165165
"""Html parser."""
166166

167-
_data_types = ["text/html", "html"]
167+
_data_types = PrivateAttr(["text/html", "html"])
168168

169169
@staticmethod
170170
def remove_hex_characters(string):
@@ -201,7 +201,11 @@ def clean_line(line):
201201
class EmailDateParser(Parser):
202202
"""Parser for Email Date."""
203203

204-
_data_types = [EMAIL_HEADER_DATE]
204+
_data_types = PrivateAttr(
205+
[
206+
EMAIL_HEADER_DATE,
207+
]
208+
)
205209

206210
def parser_hook(self, raw: bytes, content_type: str):
207211
"""Execute parsing."""
@@ -214,7 +218,11 @@ def parser_hook(self, raw: bytes, content_type: str):
214218
class EmailSubjectParser(Parser):
215219
"""Parse data from subject or email."""
216220

217-
_data_types = [EMAIL_HEADER_SUBJECT]
221+
_data_types = PrivateAttr(
222+
[
223+
EMAIL_HEADER_SUBJECT,
224+
]
225+
)
218226

219227
def parser_hook(self, raw: bytes, content_type: str):
220228
"""Execute parsing."""
@@ -236,7 +244,7 @@ def bytes_to_string(string):
236244
class Csv(Parser):
237245
"""Csv parser."""
238246

239-
_data_types = ["application/csv", "text/csv", "application/octet-stream"]
247+
_data_types = PrivateAttr(["application/csv", "text/csv", "application/octet-stream"])
240248

241249
def parser_hook(self, raw: bytes, content_type: str):
242250
"""Execute parsing."""
@@ -255,7 +263,11 @@ def parse_csv(raw: bytes) -> List[Dict]:
255263
class Text(Parser):
256264
"""Text parser."""
257265

258-
_data_types = ["text/plain"]
266+
_data_types = PrivateAttr(
267+
[
268+
"text/plain",
269+
]
270+
)
259271

260272
def parser_hook(self, raw: bytes, content_type: str):
261273
"""Execute parsing."""
@@ -278,7 +290,7 @@ def parse_text(self, text) -> List[Dict]:
278290
class LLM(Parser):
279291
"""LLM parser."""
280292

281-
_data_types = ["text/html", "html", "text/plain"]
293+
_data_types = PrivateAttr(["text/html", "html", "text/plain"])
282294

283295
_llm_question = """Please, could you extract a JSON form without any other comment,
284296
with the following JSON schema (timestamps in EPOCH and taking into account the GMT offset):

circuit_maintenance_parser/processor.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55

66
from typing import Iterable, Type, Dict, List
77

8-
from pydantic import BaseModel, Extra
9-
from pydantic.error_wrappers import ValidationError
8+
from pydantic import BaseModel, ValidationError
109

1110
from circuit_maintenance_parser.output import Maintenance, Metadata
1211
from circuit_maintenance_parser.data import NotificationData
@@ -17,7 +16,7 @@
1716
logger = logging.getLogger(__name__)
1817

1918

20-
class GenericProcessor(BaseModel, extra=Extra.forbid):
19+
class GenericProcessor(BaseModel, extra="forbid"):
2120
"""Base class for the Processors.
2221
2322
Attributes:

0 commit comments

Comments
 (0)