Skip to content

Commit ed44ec6

Browse files
committed
Migrate to Pydantic v2
1 parent bfdc04d commit ed44ec6

11 files changed

+40
-35
lines changed

src/rpft/parsers/common/model_inference.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import defaultdict
22
from typing import List, ForwardRef, _eval_type
33
from pydoc import locate
4-
from pydantic.v1 import create_model
4+
from pydantic import create_model
55

66
from rpft.parsers.common.rowparser import (
77
ParserModel,

src/rpft/parsers/common/rowparser.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections.abc import Iterable
44
from typing import List
55

6-
from pydantic.v1 import BaseModel
6+
from pydantic import BaseModel
77

88
from rpft.parsers.common.cellparser import CellParser
99

@@ -97,9 +97,7 @@ def is_basic_instance(value):
9797

9898

9999
def is_default_value(model_instance, field, field_value):
100-
# Note: In pydantic V2, __fields__ will become model_fields
101-
if field_value == type(model_instance).__fields__[field].get_default():
102-
return True
100+
return field_value == type(model_instance).model_fields[field].default
103101

104102

105103
def str_to_bool(string):
@@ -137,12 +135,12 @@ def try_assign_as_kwarg(self, field, key, value, model):
137135
# model, assign value to field[key] (which represents the field in the model)
138136
if is_list_instance(value) and len(value) == 2 and type(value[0]) is str:
139137
first_entry_as_key = model.header_name_to_field_name(value[0])
140-
if first_entry_as_key in model.__fields__:
138+
if first_entry_as_key in model.model_fields:
141139
self.assign_value(
142140
field[key],
143141
first_entry_as_key,
144142
value[1],
145-
model.__fields__[first_entry_as_key].outer_type_,
143+
model.model_fields[first_entry_as_key].annotation,
146144
)
147145
return True
148146
return False
@@ -164,7 +162,7 @@ def assign_value(self, field, key, value, model):
164162
# Get the list of keys that are available for the target model
165163
# Note: The fields have a well defined ordering.
166164
# See https://pydantic-docs.helpmanual.io/usage/models/#field-ordering
167-
model_fields = list(model.__fields__.keys())
165+
model_fields = list(model.model_fields.keys())
168166

169167
if type(value) is not list:
170168
# It could be that an object is specified via a single element.
@@ -193,7 +191,7 @@ def assign_value(self, field, key, value, model):
193191
field[key],
194192
entry_key,
195193
entry,
196-
model.__fields__[entry_key].outer_type_,
194+
model.model_fields[entry_key].annotation,
197195
)
198196
elif is_basic_dict_type(model):
199197
field[key] = {}
@@ -281,12 +279,9 @@ def find_entry(self, model, output_field, field_path):
281279
else:
282280
assert is_parser_model_type(model)
283281
key = model.header_name_to_field_name(field_name)
284-
if key not in model.__fields__:
282+
if key not in model.model_fields:
285283
raise ValueError(f"Field {key} doesn't exist in target type {model}.")
286-
child_model = model.__fields__[key].outer_type_
287-
# TODO: how does ModelField.outer_type_ and ModelField.type_
288-
# deal with nested lists, e.g. List[List[str]]?
289-
# Write test cases and fix code.
284+
child_model = model.model_fields[key].annotation
290285

291286
if key not in output_field:
292287
# Create a new entry for this, if necessary

src/rpft/parsers/creation/campaigneventrowmodel.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from pydantic import field_validator
2+
13
from rpft.parsers.common.rowparser import ParserModel
2-
from pydantic.v1 import validator
34

45

56
class CampaignEventRowModel(ParserModel):
@@ -14,13 +15,15 @@ class CampaignEventRowModel(ParserModel):
1415
flow: str = ""
1516
base_language: str = ""
1617

17-
@validator("unit")
18+
@field_validator("unit")
19+
@classmethod
1820
def validate_unit(cls, v):
1921
if v not in ["M", "H", "D", "W"]:
2022
raise ValueError("unit must be M (minute), H (hour), D (day) or W (week)")
2123
return v
2224

23-
@validator("start_mode")
25+
@field_validator("start_mode")
26+
@classmethod
2427
def validate_start_mode(cls, v):
2528
if v not in ["I", "S", "P"]:
2629
raise ValueError(
@@ -29,7 +32,8 @@ def validate_start_mode(cls, v):
2932
)
3033
return v
3134

32-
@validator("event_type")
35+
@field_validator("event_type")
36+
@classmethod
3337
def validate_event_type(cls, v):
3438
if v not in ["M", "F"]:
3539
raise ValueError("event_type must be F (flow) or M (message)")

src/rpft/parsers/creation/contentindexparser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, rows, row_model):
3636
def to_dict(self):
3737
return {
3838
"model": self.row_model.__name__,
39-
"rows": [content.dict() for content in self.rows.values()],
39+
"rows": [content.model_dump() for content in self.rows.values()],
4040
}
4141

4242

src/rpft/parsers/creation/flowrowmodel.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from pydantic import ConfigDict
2+
13
from rpft.parsers.common.rowparser import ParserModel
24
from rpft.parsers.creation.models import Condition
35

@@ -45,6 +47,8 @@ class WhatsAppTemplating(ParserModel):
4547

4648

4749
class Edge(ParserModel):
50+
model_config = ConfigDict(coerce_numbers_to_str=True)
51+
4852
from_: str = ""
4953
condition: Condition = Condition()
5054

@@ -65,6 +69,8 @@ def header_name_to_field_name_with_context(header, row):
6569

6670

6771
class FlowRowModel(ParserModel):
72+
model_config = ConfigDict(coerce_numbers_to_str=True)
73+
6874
row_id: str = ""
6975
type: str
7076
edges: list[Edge]

src/rpft/parsers/creation/triggerrowmodel.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pydantic.v1 import validator
1+
from pydantic import field_validator, model_validator
22

33
from rpft.parsers.common.rowparser import ParserModel
44

@@ -12,7 +12,8 @@ class TriggerRowModel(ParserModel):
1212
channel: str = ""
1313
match_type: str = ""
1414

15-
@validator("type")
15+
@field_validator("type")
16+
@classmethod
1617
def validate_type(cls, v):
1718
if v not in ["K", "C", "M", "T"]:
1819
raise ValueError(
@@ -21,10 +22,11 @@ def validate_type(cls, v):
2122
)
2223
return v
2324

24-
@validator("match_type")
25-
def validate_match_type(cls, v, values):
26-
if values["type"] == "K" and v not in ["F", "O", ""]:
25+
@model_validator(mode="after")
26+
def validate_match_type(self):
27+
if self.type == "K" and self.match_type not in ["F", "O", ""]:
2728
raise ValueError(
2829
'match_type must be "F" (starts with) or "O" (only) if type is "K".'
2930
)
30-
return v
31+
32+
return self

tests/test_differentways.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,9 @@ def test_different_ways(self):
124124
for inp in inputs:
125125
out = self.parser.parse_row(inp) # We get an instance of the model
126126
outputs.append(out)
127-
# Note: we can also serialize via out.json(indent=4) for printing
128-
# or out.dict()
129127

130128
for out in outputs:
131-
self.assertEqual(out.dict(), output_instance)
129+
self.assertEqual(out.model_dump(), output_instance)
132130

133131
def test_single_kwarg(self):
134132
output_single_kwarg = self.parser.parse_row(input_single_kwarg)

tests/test_flowparser_reverse.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ def setUp(self) -> None:
1010
pass
1111

1212
def compare_models_leniently(self, model, model_exp):
13-
model_dict = model.dict()
14-
model_exp_dict = model_exp.dict()
13+
model_dict = model.model_dump()
14+
model_exp_dict = model_exp.model_dump()
1515
for edge, edge_exp in zip(model_dict["edges"], model_exp_dict["edges"]):
1616
if (
1717
edge["condition"]["variable"] == "@input.text"

tests/test_model_inference.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List
22
import unittest
3-
from pydantic.v1 import create_model, BaseModel
3+
from pydantic import create_model, BaseModel
44

55
from rpft.parsers.common.model_inference import (
66
get_value_for_type,
@@ -60,7 +60,7 @@ def test_parse_header_annotations(self):
6060
self.assertEqual(parse_header_annotations("field:int=5"), (int, 5))
6161

6262
def compare_models(self, model1, model2, **kwargs):
63-
self.assertEqual(model1(**kwargs).dict(), model2(**kwargs).dict())
63+
self.assertEqual(model1(**kwargs).model_dump(), model2(**kwargs).model_dump())
6464

6565
def test_model_from_headers(self):
6666
self.compare_models(

tests/test_to_row_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ def compare_row_models_without_uuid(self, row_models1, row_models2):
4444
self.maxDiff = None
4545
self.assertEqual(len(row_models1), len(row_models2))
4646
for model1, model2 in zip(row_models1, row_models2):
47-
data1 = model1.dict()
48-
data2 = model2.dict()
47+
data1 = model1.model_dump()
48+
data2 = model2.model_dump()
4949
if not data1["node_uuid"] or not data2["node_uuid"]:
5050
# If one of them is blank, skip the comparison
5151
data1.pop("node_uuid")

tests/test_unparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class ModelWithStuff(ParserModel):
1515

1616
class MainModel(ParserModel):
1717
str_field: str = ""
18-
model_optional: Optional[ModelWithStuff]
18+
model_optional: Optional[ModelWithStuff] = None
1919
model_default: ModelWithStuff = ModelWithStuff()
2020
model_list: List[ModelWithStuff] = []
2121

0 commit comments

Comments
 (0)