Skip to content

Commit ec0d9f2

Browse files
committed
finish create method, add list method and tests, refactoring
1 parent 10113f0 commit ec0d9f2

File tree

11 files changed

+574
-139
lines changed

11 files changed

+574
-139
lines changed

application/src/vonage_application/application.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from vonage_http_client.http_client import HttpClient
55

66
from .requests import ApplicationOptions, ListApplicationsFilter
7-
from .responses import ApplicationData
7+
from .responses import ApplicationData, ListApplicationsResponse
88

99

1010
class Application:
@@ -27,22 +27,32 @@ def http_client(self) -> HttpClient:
2727
def list_applications(
2828
self, filter: ListApplicationsFilter = ListApplicationsFilter()
2929
) -> Tuple[List[ApplicationData], Optional[str]]:
30-
""""""
30+
"""List applications.
31+
32+
By default, returns the first 100 applications and the page index of
33+
the next page of results, if there are more than 100 applications.
34+
35+
Args:
36+
filter (ListApplicationsFilter): The filter object.
37+
38+
Returns:
39+
Tuple[List[ApplicationData], Optional[str]]: A tuple containing a
40+
list of applications and the next page index.
41+
"""
3142
response = self._http_client.get(
3243
self._http_client.api_host,
3344
'/v2/applications',
3445
filter.model_dump(exclude_none=True),
3546
self._auth_type,
3647
)
3748

38-
# applications_response = ListApplicationsResponse(**response)
39-
# if applications_response.links.next is None:
40-
# return applications_response.embedded.users, None
49+
applications_response = ListApplicationsResponse(**response)
50+
51+
if applications_response.page == applications_response.total_pages:
52+
return applications_response.embedded.applications, None
4153

42-
# parsed_url = urlparse(users_response.links.next.href)
43-
# query_params = parse_qs(parsed_url.query)
44-
# next_cursor = query_params.get('cursor', [None])[0]
45-
# return users_response.embedded.users, next_cursor
54+
next_page = applications_response.page + 1
55+
return applications_response.embedded.applications, next_page
4656

4757
@validate_call
4858
def create_application(
Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
1-
from typing import Literal, Optional, Union
1+
from typing import Literal, Optional
22

33
from pydantic import BaseModel, Field, field_validator
44

55
from .enums import Region
66
from .errors import ApplicationError
77

88

9-
class Url(BaseModel):
9+
class ApplicationUrl(BaseModel):
1010
address: str
1111
http_method: Optional[Literal['GET', 'POST']] = None
1212

1313

14-
class VoiceUrl(Url):
15-
connection_timeout: Optional[int] = Field(None, ge=300, le=1000)
16-
socket_timeout: Optional[int] = Field(None, ge=1000, le=5000)
14+
class VoiceUrl(ApplicationUrl):
15+
connect_timeout: Optional[int] = Field(None, ge=300, le=1000)
16+
socket_timeout: Optional[int] = Field(None, ge=1000, le=10000)
1717

1818

1919
class VoiceWebhooks(BaseModel):
20-
answer_url: Optional[Url] = None
21-
fallback_answer_url: Optional[Url] = None
22-
event_url: Optional[Url] = None
20+
answer_url: Optional[VoiceUrl] = None
21+
fallback_answer_url: Optional[VoiceUrl] = None
22+
event_url: Optional[VoiceUrl] = None
2323

2424

2525
class Voice(BaseModel):
26+
"""Voice application capabilities."""
27+
2628
webhooks: Optional[VoiceWebhooks] = None
2729
signed_callbacks: Optional[bool] = None
2830
conversations_ttl: Optional[int] = Field(None, ge=1, le=9000)
@@ -31,40 +33,60 @@ class Voice(BaseModel):
3133

3234

3335
class RtcWebhooks(BaseModel):
34-
event_url: Optional[Url] = None
36+
event_url: Optional[ApplicationUrl] = None
3537

3638

3739
class Rtc(BaseModel):
40+
"""Real-Time Communications application capabilities."""
41+
3842
webhooks: Optional[RtcWebhooks] = None
3943
signed_callbacks: Optional[bool] = None
4044

4145

4246
class MessagesWebhooks(BaseModel):
43-
inbound_url: Optional[Url] = None
44-
status_url: Optional[Url] = None
47+
inbound_url: Optional[ApplicationUrl] = None
48+
status_url: Optional[ApplicationUrl] = None
49+
50+
@field_validator('inbound_url', 'status_url')
51+
@classmethod
52+
def check_http_method(cls, v: ApplicationUrl):
53+
if v.http_method is not None and v.http_method != 'POST':
54+
raise ApplicationError('HTTP method must be POST')
55+
return v
4556

4657

4758
class Messages(BaseModel):
48-
version: Optional[str] = None
59+
"""Messages application capabilities."""
60+
4961
webhooks: Optional[MessagesWebhooks] = None
62+
version: Optional[str] = None
63+
authenticate_inbound_media: Optional[bool] = None
5064

5165

5266
class Vbc(BaseModel):
53-
pass
67+
"""VBC capabilities.
68+
69+
This object should be empty when creating or updating an application.
70+
"""
5471

5572

5673
class VerifyWebhooks(BaseModel):
57-
status_url: Optional[Url] = None
74+
status_url: Optional[ApplicationUrl] = None
5875

5976
@field_validator('status_url')
6077
@classmethod
61-
def check_http_method(cls, v: Url):
78+
def check_http_method(cls, v: ApplicationUrl):
6279
if v.http_method is not None and v.http_method != 'POST':
6380
raise ApplicationError('HTTP method must be POST')
6481
return v
6582

6683

6784
class Verify(BaseModel):
85+
"""Verify application capabilities.
86+
87+
Don't set the `version` field when creating or updating an application.
88+
"""
89+
6890
webhooks: Optional[VerifyWebhooks] = None
6991
version: Optional[str] = None
7092

@@ -73,7 +95,18 @@ class Privacy(BaseModel):
7395
improve_ai: Optional[bool] = None
7496

7597

98+
class Capabilities(BaseModel):
99+
voice: Optional[Voice] = None
100+
rtc: Optional[Rtc] = None
101+
messages: Optional[Messages] = None
102+
vbc: Optional[Vbc] = None
103+
verify: Optional[Verify] = None
104+
105+
76106
class ApplicationBase(BaseModel):
107+
"""Base application object used in requests and responses when communicating with the Vonage
108+
Application API."""
109+
77110
name: str
78-
capabilities: Optional[Union[Voice, Rtc, Messages, Vbc, Verify]] = None
111+
capabilities: Optional[Capabilities] = None
79112
privacy: Optional[Privacy] = None

application/src/vonage_application/requests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77

88
class ListApplicationsFilter(BaseModel):
9-
"""Request object for listing users."""
9+
"""Request object for filtering applications."""
1010

1111
page_size: Optional[int] = 100
1212
page: int = None
1313

1414

15-
class KeysRequest(BaseModel):
15+
class RequestKeys(BaseModel):
1616
public_key: str
1717

1818

1919
class ApplicationOptions(ApplicationBase):
20-
keys: Optional[KeysRequest] = None
20+
keys: Optional[RequestKeys] = None
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
1-
from typing import Optional
1+
from typing import List, Optional
22

33
from pydantic import BaseModel, Field, model_validator
4-
from vonage_utils.models import ResourceLink
4+
from vonage_utils.models import HalLinks, ResourceLink
55

66
from .common import ApplicationBase
77

8-
# class Embedded(BaseModel):
9-
# users: List[UserSummary] = []
108

11-
12-
# class ListApplicationsResponse(BaseModel):
13-
# page_size: int
14-
# embedded: Embedded = Field(..., validation_alias='_embedded')
15-
# links: Links = Field(..., validation_alias='_links')
16-
17-
18-
class KeysResponse(BaseModel):
9+
class ResponseKeys(BaseModel):
1910
public_key: Optional[str] = None
2011
private_key: Optional[str] = None
2112

2213

2314
class ApplicationData(ApplicationBase):
2415
id: str
25-
keys: Optional[KeysResponse] = None
16+
keys: Optional[ResponseKeys] = None
2617
links: Optional[ResourceLink] = Field(None, validation_alias='_links', exclude=True)
2718
link: Optional[str] = None
2819

@@ -31,3 +22,16 @@ def get_link(self):
3122
if self.links is not None:
3223
self.link = self.links.self.href
3324
return self
25+
26+
27+
class Embedded(BaseModel):
28+
applications: List[ApplicationData] = []
29+
30+
31+
class ListApplicationsResponse(BaseModel):
32+
page_size: Optional[int] = None
33+
page: int = Field(None, ge=1)
34+
total_items: Optional[int] = None
35+
total_pages: Optional[int] = None
36+
embedded: Embedded = Field(..., validation_alias='_embedded')
37+
links: HalLinks = Field(..., validation_alias='_links')
Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,75 @@
11
{
2-
"id": "ba1a6aa3-8ac6-487d-ac5c-be469e77ddb7",
3-
"name": "My Application",
2+
"id": "33e3329f-d1cc-48f3-9105-55e5a6e475c1",
3+
"name": "My Customised Application",
44
"keys": {
5-
"private_key": "-----BEGIN PRIVATE KEY-----\nprivate_key_info_goes_here\n-----END PRIVATE KEY-----\n",
65
"public_key": "-----BEGIN PUBLIC KEY-----\npublic_key_info_goes_here\n-----END PUBLIC KEY-----\n"
76
},
87
"privacy": {
98
"improve_ai": false
109
},
11-
"capabilities": {},
10+
"capabilities": {
11+
"voice": {
12+
"webhooks": {
13+
"event_url": {
14+
"address": "https://example.com/event",
15+
"http_method": "POST",
16+
"socket_timeout": 3000,
17+
"connect_timeout": 500
18+
},
19+
"answer_url": {
20+
"address": "https://example.com/answer",
21+
"http_method": "POST",
22+
"socket_timeout": 3000,
23+
"connect_timeout": 500
24+
},
25+
"fallback_answer_url": {
26+
"address": "https://example.com/fallback",
27+
"http_method": "POST",
28+
"socket_timeout": 3000,
29+
"connect_timeout": 500
30+
}
31+
},
32+
"signed_callbacks": true,
33+
"conversations_ttl": 8000,
34+
"leg_persistence_time": 14,
35+
"region": "na-east"
36+
},
37+
"rtc": {
38+
"webhooks": {
39+
"event_url": {
40+
"address": "https://example.com/event",
41+
"http_method": "POST"
42+
}
43+
},
44+
"signed_callbacks": true
45+
},
46+
"messages": {
47+
"webhooks": {
48+
"inbound_url": {
49+
"address": "https://example.com/inbound",
50+
"http_method": "POST"
51+
},
52+
"status_url": {
53+
"address": "https://example.com/status",
54+
"http_method": "POST"
55+
}
56+
},
57+
"version": "v1",
58+
"authenticate_inbound_media": true
59+
},
60+
"verify": {
61+
"webhooks": {
62+
"status_url": {
63+
"address": "https://example.com/status",
64+
"http_method": "POST"
65+
}
66+
}
67+
},
68+
"vbc": {}
69+
},
1270
"_links": {
1371
"self": {
14-
"href": "/v2/applications/ba1a6aa3-8ac6-487d-ac5c-be469e77ddb7"
72+
"href": "/v2/applications/33e3329f-d1cc-48f3-9105-55e5a6e475c1"
1573
}
1674
}
1775
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"page_size": 100,
3+
"page": 1,
4+
"total_items": 1,
5+
"total_pages": 1,
6+
"_embedded": {
7+
"applications": [
8+
{
9+
"id": "1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b",
10+
"name": "dev-application",
11+
"keys": {
12+
"public_key": "-----BEGIN PUBLIC KEY-----\npublic_key_info_goes_here\n-----END PUBLIC KEY-----\n"
13+
},
14+
"privacy": {
15+
"improve_ai": true
16+
},
17+
"capabilities": {
18+
"voice": {
19+
"webhooks": {
20+
"event_url": {
21+
"address": "http://example.com",
22+
"http_method": "POST"
23+
},
24+
"answer_url": {
25+
"address": "http://example.com",
26+
"http_method": "GET"
27+
}
28+
},
29+
"signed_callbacks": true,
30+
"conversations_ttl": 9000,
31+
"leg_persistence_time": 7
32+
}
33+
}
34+
}
35+
]
36+
},
37+
"_links": {
38+
"self": {
39+
"href": "/v2/applications?page_size=100&page=1"
40+
},
41+
"first": {
42+
"href": "/v2/applications?page_size=100"
43+
},
44+
"last": {
45+
"href": "/v2/applications?page_size=100&page=1"
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)