Skip to content

Commit 0a8d217

Browse files
Migrate to pydantic 2 (#291)
* Upgrade & fix models & tests * address deprecated pydantic warnings * remove print statments, uncomment test * fix optional -> None fields
1 parent c09833c commit 0a8d217

File tree

15 files changed

+260
-202
lines changed

15 files changed

+260
-202
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pytest==7.4.2
33
responses==0.22.0
44
coverage
5-
pydantic>=1.10,==1.*
5+
pydantic==2.5.2
66

77
bump2version
88
build

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"requests>=2.4.2",
2626
"pytz>=2018.5",
2727
"Deprecated",
28-
"pydantic>=1.10,==1.*",
28+
"pydantic>=2.5.2",
2929
],
3030
python_requires=">=3.8",
3131
tests_require=["cryptography>=2.3.1"],

src/vonage/ncco_builder/connect_endpoints.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,62 @@
1-
from pydantic import BaseModel, HttpUrl, AnyUrl, Field, constr
2-
from typing import Optional, Dict
1+
from pydantic import BaseModel, HttpUrl, AnyUrl, constr, field_serializer
2+
from typing import Dict
33
from typing_extensions import Literal
44

55

66
class ConnectEndpoints:
77
class Endpoint(BaseModel):
8-
type: str = None
8+
type: Literal['phone', 'app', 'websocket', 'sip', 'vbc'] = None
99

1010
class PhoneEndpoint(Endpoint):
11-
type = Field('phone', const=True)
12-
number: constr(regex=r'^[1-9]\d{6,14}$')
13-
dtmfAnswer: Optional[constr(regex='^[0-9*#p]+$')]
14-
onAnswer: Optional[Dict[str, HttpUrl]]
11+
type: Literal['phone'] = 'phone'
12+
13+
number: constr(pattern=r'^[1-9]\d{6,14}$')
14+
dtmfAnswer: constr(pattern='^[0-9*#p]+$') = None
15+
onAnswer: Dict[str, HttpUrl] = None
16+
17+
@field_serializer('onAnswer')
18+
def serialize_dt(self, oa: Dict[str, HttpUrl], _info):
19+
if oa is None:
20+
return oa
21+
22+
return {k: str(v) for k, v in oa.items()}
1523

1624
class AppEndpoint(Endpoint):
17-
type = Field('app', const=True)
25+
type: Literal['app'] = 'app'
1826
user: str
1927

2028
class WebsocketEndpoint(Endpoint):
21-
type = Field('websocket', const=True)
29+
type: Literal['websocket'] = 'websocket'
30+
2231
uri: AnyUrl
2332
contentType: Literal['audio/l16;rate=16000', 'audio/l16;rate=8000']
24-
headers: Optional[dict]
33+
headers: dict = None
34+
35+
@field_serializer('uri')
36+
def serialize_uri(self, uri: AnyUrl, _info):
37+
return str(uri)
2538

2639
class SipEndpoint(Endpoint):
27-
type = Field('sip', const=True)
40+
type: Literal['sip'] = 'sip'
2841
uri: str
29-
headers: Optional[dict]
42+
headers: dict = None
3043

3144
class VbcEndpoint(Endpoint):
32-
type = Field('vbc', const=True)
45+
type: Literal['vbc'] = 'vbc'
3346
extension: str
3447

3548
@classmethod
3649
def create_endpoint_model_from_dict(cls, d) -> Endpoint:
3750
if d['type'] == 'phone':
38-
return cls.PhoneEndpoint.parse_obj(d)
51+
return cls.PhoneEndpoint.model_validate(d)
3952
elif d['type'] == 'app':
40-
return cls.AppEndpoint.parse_obj(d)
53+
return cls.AppEndpoint.model_validate(d)
4154
elif d['type'] == 'websocket':
42-
return cls.WebsocketEndpoint.parse_obj(d)
55+
return cls.WebsocketEndpoint.model_validate(d)
4356
elif d['type'] == 'sip':
44-
return cls.WebsocketEndpoint.parse_obj(d)
57+
return cls.WebsocketEndpoint.model_validate(d)
4558
elif d['type'] == 'vbc':
46-
return cls.WebsocketEndpoint.parse_obj(d)
59+
return cls.WebsocketEndpoint.model_validate(d)
4760
else:
4861
raise ValueError(
4962
'Invalid "type" specified for endpoint object. Cannot create a ConnectEndpoints.Endpoint model.'
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
from pydantic import BaseModel, confloat, conint
2-
from typing import Optional, List
2+
from typing import List
33

44

55
class InputTypes:
66
class Dtmf(BaseModel):
7-
timeOut: Optional[conint(ge=0, le=10)]
8-
maxDigits: Optional[conint(ge=1, le=20)]
9-
submitOnHash: Optional[bool]
7+
timeOut: conint(ge=0, le=10) = None
8+
maxDigits: conint(ge=1, le=20) = None
9+
submitOnHash: bool = None
1010

1111
class Speech(BaseModel):
12-
uuid: Optional[str]
13-
endOnSilence: Optional[confloat(ge=0.4, le=10.0)]
14-
language: Optional[str]
15-
context: Optional[List[str]]
16-
startTimeout: Optional[conint(ge=1, le=60)]
17-
maxDuration: Optional[conint(ge=1, le=60)]
18-
saveAudio: Optional[bool]
12+
uuid: str = None
13+
endOnSilence: confloat(ge=0.4, le=10.0) = None
14+
language: str = None
15+
context: List[str] = None
16+
startTimeout: conint(ge=1, le=60) = None
17+
maxDuration: conint(ge=1, le=60) = None
18+
saveAudio: bool = None
1919

2020
@classmethod
2121
def create_dtmf_model(cls, dict) -> Dtmf:
22-
return cls.Dtmf.parse_obj(dict)
22+
return cls.Dtmf.model_validate(dict)
2323

2424
@classmethod
2525
def create_speech_model(cls, dict) -> Speech:
26-
return cls.Speech.parse_obj(dict)
26+
return cls.Speech.model_validate(dict)

0 commit comments

Comments
 (0)