Skip to content

Commit 1ae0990

Browse files
committed
add account api, prepare for new releases
1 parent 21ea88c commit 1ae0990

File tree

19 files changed

+308
-188
lines changed

19 files changed

+308
-188
lines changed

account/README.md

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,66 @@
1-
# Vonage Users Package
1+
# Vonage Account Package
22

3-
This package contains the code to use Vonage's Application API in Python.
3+
This package contains the code to use Vonage's Account API in Python.
44

5-
It includes methods for managing applications.
5+
It includes methods for managing Vonage accounts.
66

77
## Usage
88

99
It is recommended to use this as part of the main `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`.
1010

11-
### List Applications
1211

13-
With no custom options specified, this method will get the first 100 applications. It returns a tuple consisting of a list of `ApplicationData` objects and an int showing the page number of the next page of results.
12+
### Get Account Balance
1413

1514
```python
16-
from vonage_application import ListApplicationsFilter, ApplicationData
15+
balance = vonage_client.account.get_balance()
16+
print(balance)
17+
```
1718

18-
applications, next_page = vonage_client.application.list_applications()
19+
### Top-Up Account
1920

20-
# With options
21-
options = ListApplicationsFilter(page_size=3, page=2)
22-
applications, next_page = vonage_client.application.list_applications(options)
21+
```python
22+
response = vonage_client.account.top_up(trx='1234567890')
23+
print(response)
2324
```
2425

25-
### Create a New Application
26+
### Update the Default SMS Webhook
27+
28+
This will return a Pydantic object (`SettingsResponse`) containing multiple settings for your account.
2629

2730
```python
28-
from vonage_application import ApplicationConfig
29-
30-
app_data = vonage_client.application.create_application()
31-
32-
# Create with custom options (can also be done with a dict)
33-
from vonage_application import ApplicationConfig, Keys, Voice, VoiceWebhooks
34-
voice = Voice(
35-
webhooks=VoiceWebhooks(
36-
event_url=VoiceUrl(
37-
address='https://example.com/event',
38-
http_method='POST',
39-
connect_timeout=500,
40-
socket_timeout=3000,
41-
),
42-
),
43-
signed_callbacks=True,
31+
settings: SettingsResponse = vonage_client.account.update_default_sms_webhook(
32+
mo_callback_url='https://example.com/inbound_sms_webhook',
33+
dr_callback_url='https://example.com/delivery_receipt_webhook',
4434
)
45-
capabilities = Capabilities(voice=voice)
46-
keys = Keys(public_key='MY_PUBLIC_KEY')
47-
config = ApplicationConfig(
48-
name='My Customised Application',
49-
capabilities=capabilities,
50-
keys=keys,
51-
)
52-
app_data = vonage_client.application.create_application(config)
35+
36+
print(settings)
5337
```
5438

55-
### Get an Application
39+
### List Secrets Associated with the Account
5640

5741
```python
58-
app_data = client.application.get_application('MY_APP_ID')
59-
app_data_as_dict = app.model_dump(exclude_none=True)
42+
response = vonage_client.account.list_secrets()
43+
print(response)
6044
```
6145

62-
### Update an Application
63-
64-
To update an application, pass config for the updated field(s) in an ApplicationConfig object
46+
### Create a New Account Secret
6547

6648
```python
67-
from vonage_application import ApplicationConfig, Keys, Voice, VoiceWebhooks
49+
secret = vonage_client.account.create_secret('Mytestsecret12345')
50+
print(secret)
51+
```
52+
53+
### Get Information About One Secret
6854

69-
config = ApplicationConfig(name='My Updated Application')
70-
app_data = vonage_client.application.update_application('MY_APP_ID', config)
55+
```python
56+
secret = vonage_client.account.get_secret(MY_SECRET_ID)
57+
print(secret)
7158
```
7259

73-
### Delete an Application
60+
### Revoke a Secret
61+
62+
Note: it isn't possible to revoke all account secrets, there must always be one valid secret. Attempting to do so will give a 403 error.
7463

7564
```python
76-
vonage_client.applications.delete_application('MY_APP_ID')
77-
```
65+
client.account.revoke_secret(MY_SECRET_ID)
66+
```

account/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
77
requires-python = ">=3.8"
88
dependencies = [
9-
"vonage-http-client>=1.3.1",
9+
"vonage-http-client>=1.4.0",
1010
"vonage-utils>=1.1.2",
1111
"pydantic>=2.7.1",
1212
]
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
from .account import Account
2+
from .errors import InvalidSecretError
3+
from .responses import Balance, SettingsResponse, TopUpResponse, VonageApiSecret
24

3-
__all__ = ['Account']
5+
__all__ = [
6+
'Account',
7+
'Balance',
8+
'InvalidSecretError',
9+
'SettingsResponse',
10+
'TopUpResponse',
11+
'VonageApiSecret',
12+
]

account/src/vonage_account/account.py

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import re
2+
from typing import List
3+
14
from pydantic import validate_call
5+
from vonage_account.errors import InvalidSecretError
26
from vonage_http_client.http_client import HttpClient
37

4-
from .requests import Balance
5-
from .responses import SettingsResponse, TopUpResponse
8+
from .responses import Balance, SettingsResponse, TopUpResponse, VonageApiSecret
69

710

811
class Account:
@@ -21,7 +24,6 @@ def http_client(self) -> HttpClient:
2124
"""
2225
return self._http_client
2326

24-
@validate_call
2527
def get_balance(self) -> Balance:
2628
"""Get the balance of the account.
2729
@@ -62,8 +64,8 @@ def top_up(self, trx: str) -> TopUpResponse:
6264
def update_default_sms_webhook(
6365
self, mo_callback_url: str = None, dr_callback_url: str = None
6466
) -> SettingsResponse:
65-
"""Update the default SMS webhook URLs for the account.
66-
In order to unset any default value, pass an empty string as the value.
67+
"""Update the default SMS webhook URLs for the account. In order to unset any default value,
68+
pass an empty string as the value.
6769
6870
Args:
6971
mo_callback_url (str, optional): The URL to which inbound SMS messages will be
@@ -89,10 +91,89 @@ def update_default_sms_webhook(
8991
)
9092
return SettingsResponse(**response)
9193

92-
def list_secrets(self) -> SecretList:
94+
def list_secrets(self) -> List[VonageApiSecret]:
9395
"""List all secrets associated with the account.
9496
9597
Returns:
96-
SecretList: List of Secret objects.
98+
List[VonageApiSecret]: List of VonageApiSecret objects.
99+
"""
100+
response = self._http_client.get(
101+
self._http_client.api_host,
102+
f'/accounts/{self._http_client.auth.api_key}/secrets',
103+
auth_type=self._auth_type,
104+
)
105+
secrets = []
106+
for element in response['_embedded']['secrets']:
107+
secrets.append(VonageApiSecret(**element))
108+
109+
return secrets
110+
111+
@validate_call
112+
def create_secret(self, secret: str) -> VonageApiSecret:
113+
"""Create an API secret for the account.
114+
115+
Args:
116+
secret (VonageSecret): The secret to create. Must satisfy the following
117+
conditions:
118+
- 8-25 characters long
119+
- At least one uppercase letter
120+
- At least one lowercase letter
121+
- At least one digit
122+
123+
Returns:
124+
VonageApiSecret: The created VonageApiSecret object.
97125
"""
98-
pass
126+
if not self._is_valid_secret(secret):
127+
raise InvalidSecretError(
128+
'Secret must be 8-25 characters long and contain at least one uppercase '
129+
'letter, one lowercase letter, and one digit.'
130+
)
131+
132+
response = self._http_client.post(
133+
self._http_client.api_host,
134+
f'/accounts/{self._http_client.auth.api_key}/secrets',
135+
params={'secret': secret},
136+
auth_type=self._auth_type,
137+
)
138+
return VonageApiSecret(**response)
139+
140+
@validate_call
141+
def get_secret(self, secret_id: str) -> VonageApiSecret:
142+
"""Get a specific secret associated with the account.
143+
144+
Args:
145+
secret_id (str): The ID of the secret to retrieve.
146+
147+
Returns:
148+
VonageApiSecret: The VonageApiSecret object.
149+
"""
150+
response = self._http_client.get(
151+
self._http_client.api_host,
152+
f'/accounts/{self._http_client.auth.api_key}/secrets/{secret_id}',
153+
auth_type=self._auth_type,
154+
)
155+
return VonageApiSecret(**response)
156+
157+
@validate_call
158+
def revoke_secret(self, secret_id: str) -> None:
159+
"""Revoke a specific secret associated with the account.
160+
161+
Args:
162+
secret_id (str): The ID of the secret to revoke.
163+
"""
164+
self._http_client.delete(
165+
self._http_client.api_host,
166+
f'/accounts/{self._http_client.auth.api_key}/secrets/{secret_id}',
167+
auth_type=self._auth_type,
168+
)
169+
170+
def _is_valid_secret(self, secret: str) -> bool:
171+
if len(secret) < 8 or len(secret) > 25:
172+
return False
173+
if not re.search(r'[a-z]', secret):
174+
return False
175+
if not re.search(r'[A-Z]', secret):
176+
return False
177+
if not re.search(r'\d', secret):
178+
return False
179+
return True

account/src/vonage_account/errors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from vonage_utils.errors import VonageError
22

33

4-
class AccountError(VonageError):
5-
"""Indicates an error with the Account package."""
4+
class InvalidSecretError(VonageError):
5+
"""Indicates that the secret provided was invalid."""

account/src/vonage_account/requests.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

account/src/vonage_account/responses.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
from pydantic import BaseModel, Field
44

55

6+
class Balance(BaseModel):
7+
value: float
8+
auto_reload: Optional[bool] = Field(None, validation_alias='autoReload')
9+
10+
611
class TopUpResponse(BaseModel):
712
error_code: Optional[str] = Field(None, validation_alias='error-code')
813
error_code_label: Optional[str] = Field(None, validation_alias='error-code-label')
@@ -20,3 +25,8 @@ class SettingsResponse(BaseModel):
2025
max_calls_per_second: Optional[int] = Field(
2126
None, validation_alias='max-calls-per-second'
2227
)
28+
29+
30+
class VonageApiSecret(BaseModel):
31+
id: str
32+
created_at: str
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "https://developer.nexmo.com/api-errors/account/secret-management#add-excess-secret",
3+
"title": "Secret Addition Forbidden",
4+
"detail": "Account reached maximum number [2] of allowed secrets",
5+
"instance": "48898273-7ae1-4ce4-8125-a71058ca6069"
6+
}

account/tests/data/list_secrets.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"_links": {
3+
"self": {
4+
"href": "/accounts/test_api_key/secrets"
5+
}
6+
},
7+
"_embedded": {
8+
"secrets": [
9+
{
10+
"_links": {
11+
"self": {
12+
"href": "/accounts/test_api_key/secrets/1b1b1b1b-1b1b-1b-1b1b-1b1b1b1b1b1b"
13+
}
14+
},
15+
"id": "1b1b1b1b-1b1b-1b-1b1b-1b1b1b1b1b1b",
16+
"created_at": "2022-03-28T14:16:56Z"
17+
}
18+
]
19+
}
20+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "https://developer.nexmo.com/api-errors/account/secret-management#delete-last-secret",
3+
"title": "Secret Deletion Forbidden",
4+
"detail": "Can not delete the last secret. The account must always have at least 1 secret active at any time",
5+
"instance": "a845d164-5623-4cc1-b7c6-0f95b94c6e53"
6+
}

0 commit comments

Comments
 (0)