Skip to content

Commit ba75d68

Browse files
author
Mark Smith
authored
Applications v2 (#119)
* Add `application_v2` to `nexmo.Client` * Add deprecation warnings to the legacy application methods in `nexmo.Client`
1 parent c530054 commit ba75d68

File tree

15 files changed

+605
-62
lines changed

15 files changed

+605
-62
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,4 @@ ENV*
101101
/site
102102

103103
.requirements.txt
104+
*_quickstart*

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Alternatively, you can clone the repository via the command line:
3737
git clone [email protected]:Nexmo/nexmo-python.git
3838

3939
or by opening it on GitHub desktop.
40-
40+
4141

4242
Usage
4343
-----
@@ -178,7 +178,7 @@ response = client.send_dtmf(uuid, digits='1234')
178178

179179
Docs: [https://developer.nexmo.com/api/voice#startDTMF](https://developer.nexmo.com/api/voice?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#startDTMF)
180180

181-
### Get recording
181+
### Get recording
182182

183183
``` python
184184
response = client.get_recording(RECORDING_URL)
@@ -294,42 +294,42 @@ client.delete_secret(API_KEY, 'my-secret-id')
294294
### Create an application
295295

296296
```python
297-
response = client.create_application(name='Example App', type='voice', answer_url=answer_url, event_url=event_url)
297+
response = client.application_v2.create_application({name='Example App', type='voice'})
298298
```
299299

300-
Docs: [https://developer.nexmo.com/api/application#create-an-application](https://developer.nexmo.com/api/application?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#create-an-application)
300+
Docs: [https://developer.nexmo.com/api/application.v2#createApplication](https://developer.nexmo.com/api/application.v2#createApplication?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#create-an-application)
301301

302302
### Retrieve a list of applications
303303

304304
```python
305-
response = client.get_applications()
305+
response = client.application_v2.list_applications()
306306
```
307307

308-
Docs: [https://developer.nexmo.com/api/application#retrieve-your-applications](https://developer.nexmo.com/api/application?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#retrieve-your-applications)
308+
Docs: [https://developer.nexmo.com/api/application.v2#listApplication](https://developer.nexmo.com/api/application.v2#listApplication?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#retrieve-your-applications)
309309

310310
### Retrieve a single application
311311

312312
```python
313-
response = client.get_application(uuid)
313+
response = client.application_v2.get_application(uuid)
314314
```
315315

316-
Docs: [https://developer.nexmo.com/api/application#retrieve-an-application](https://developer.nexmo.com/api/application?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#retrieve-an-application)
316+
Docs: [https://developer.nexmo.com/api/application.v2#getApplication](https://developer.nexmo.com/api/application.v2#getApplication?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#retrieve-an-application)
317317

318318
### Update an application
319319

320320
```python
321-
response = client.update_application(uuid, answer_method='POST')
321+
response = client.application_v2.update_application(uuid, answer_method='POST')
322322
```
323323

324-
Docs: [https://developer.nexmo.com/api/application#update-an-application](https://developer.nexmo.com/api/application?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#update-an-application)
324+
Docs: [https://developer.nexmo.com/api/application.v2#updateApplication](https://developer.nexmo.com/api/application.v2#updateApplication?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#update-an-application)
325325

326326
### Delete an application
327327

328328
```python
329-
response = client.delete_application(uuid)
329+
response = client.application_v2.delete_application(uuid)
330330
```
331331

332-
Docs: [https://developer.nexmo.com/api/application#destroy-an-application](https://developer.nexmo.com/api/application?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#destroy-an-application)
332+
Docs: [https://developer.nexmo.com/api/application.v2#deleteApplication](https://developer.nexmo.com/api/application.v2#deleteApplication?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#destroy-an-application)
333333

334334

335335
## Validate webhook signatures

docs/_static/.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
# Created by https://www.gitignore.io/api/osx
3+
# Edit at https://www.gitignore.io/?templates=osx
4+
5+
### OSX ###
6+
# General
7+
.DS_Store
8+
.AppleDouble
9+
.LSOverride
10+
11+
# Icon must end with two \r
12+
Icon
13+
14+
# Thumbnails
15+
._*
16+
17+
# Files that might appear in the root of a volume
18+
.DocumentRevisions-V100
19+
.fseventsd
20+
.Spotlight-V100
21+
.TemporaryItems
22+
.Trashes
23+
.VolumeIcon.icns
24+
.com.apple.timemachine.donotpresent
25+
26+
# Directories potentially created on remote AFP share
27+
.AppleDB
28+
.AppleDesktop
29+
Network Trash Folder
30+
Temporary Items
31+
.apdisk
32+
33+
# End of https://www.gitignore.io/api/osx

docs/reference.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
API Reference
22
=============
33

4-
.. automodule:: nexmo
4+
.. autoclass:: nexmo.Client
55
:members:
6-
:undoc-members:
6+
:undoc-members:
7+
8+
.. attribute:: application_v2
9+
10+
An instance of :class:`nexmo.ApplicationV2` for accessing the Application API.
11+
12+
.. autoclass:: nexmo.ApplicationV2
13+
:members:
14+
:undoc-members:

nexmo/__init__.py

Lines changed: 92 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,44 @@
2727
except ImportError:
2828
JSONDecodeError = ValueError
2929

30+
from .errors import *
31+
from ._internal import ApplicationV2, BasicAuthenticatedServer, _format_date_param
32+
3033
__version__ = "2.3.0"
3134

3235
logger = logging.getLogger("nexmo")
3336

3437

35-
class Error(Exception):
36-
pass
37-
38-
39-
class ClientError(Error):
40-
pass
41-
42-
43-
class ServerError(Error):
44-
pass
45-
46-
47-
class AuthenticationError(ClientError):
48-
pass
49-
50-
5138
class Client:
39+
"""
40+
Create a Client object to start making calls to Nexmo APIs.
41+
42+
Most methods corresponding to Nexmo API calls are on this class itself,
43+
although newer APIs are under namespaces like :attr:`Client.application_v2`.
44+
45+
The credentials you provide when instantiating a Client determine which
46+
methods can be called. Consult the `Nexmo API docs <https://developer.nexmo.com/api/>`_ for details of the
47+
authentication used by the APIs you wish to use, and instantiate your
48+
Client with the appropriate credentials.
49+
50+
:param str key: Your Nexmo API key
51+
:param str secret: Your Nexmo API secret.
52+
:param str signature_secret: Your Nexmo API signature secret.
53+
You may need to have this enabled by Nexmo support. It is only used for SMS authentication.
54+
:param str signature_method:
55+
The encryption method used for signature encryption. This must match the method
56+
configured in the Nexmo Dashboard. We recommend `sha256` or `sha512`.
57+
This should be one of `md5`, `sha1`, `sha256`, or `sha512` if using HMAC digests.
58+
If you want to use a simple MD5 hash, leave this as `None`.
59+
:param str application_id: Your application ID if calling methods which use JWT authentication.
60+
:param str private_key: Your private key if calling methods which use JWT authentication.
61+
This should either be a str containing the key in its PEM form, or a path to a private key file.
62+
:param str app_name: This optional value is added to the user-agent header
63+
provided by this library and can be used by Nexmo to track your app statistics.
64+
:param str app_name: This optional value is added to the user-agent header
65+
provided by this library and can be used by Nexmo to track your app statistics.
66+
"""
67+
5268
def __init__(
5369
self,
5470
key=None,
@@ -71,7 +87,7 @@ def __init__(
7187
"NEXMO_SIGNATURE_METHOD", None
7288
)
7389

74-
if signature_method in {"md5", "sha1", "sha256", "sha512"}:
90+
if self.signature_method in {"md5", "sha1", "sha256", "sha512"}:
7591
self.signature_method = getattr(hashlib, signature_method)
7692

7793
self.application_id = application_id
@@ -99,6 +115,14 @@ def __init__(
99115

100116
self.auth_params = {}
101117

118+
api_server = BasicAuthenticatedServer(
119+
"https://api.nexmo.com",
120+
user_agent=user_agent,
121+
api_key=self.api_key,
122+
api_secret=self.api_secret,
123+
)
124+
self.application_v2 = ApplicationV2(api_server)
125+
102126
def auth(self, params=None, **kwargs):
103127
self.auth_params = params or kwargs
104128

@@ -297,25 +321,50 @@ def request_number_insight(self, params=None, **kwargs):
297321
return self.post(self.host, "/ni/json", params or kwargs)
298322

299323
def get_applications(self, params=None, **kwargs):
324+
warnings.warn(
325+
"nexmo.Client#get_applications is deprecated (use methods from #application_v2 instead)",
326+
DeprecationWarning,
327+
stacklevel=2,
328+
)
300329
return self.get(self.api_host, "/v1/applications", params or kwargs)
301330

302331
def get_application(self, application_id):
332+
warnings.warn(
333+
"nexmo.Client#get_application is deprecated (use methods from #application_v2 instead)",
334+
DeprecationWarning,
335+
stacklevel=2,
336+
)
303337
return self.get(
304338
self.api_host,
305339
"/v1/applications/{application_id}".format(application_id=application_id),
306340
)
307341

308342
def create_application(self, params=None, **kwargs):
343+
warnings.warn(
344+
"nexmo.Client#create_application is deprecated (use methods from #application_v2 instead)",
345+
DeprecationWarning,
346+
stacklevel=2,
347+
)
309348
return self.post(self.api_host, "/v1/applications", params or kwargs)
310349

311350
def update_application(self, application_id, params=None, **kwargs):
351+
warnings.warn(
352+
"nexmo.Client#update_application is deprecated (use methods from #application_v2 instead)",
353+
DeprecationWarning,
354+
stacklevel=2,
355+
)
312356
return self.put(
313357
self.api_host,
314358
"/v1/applications/{application_id}".format(application_id=application_id),
315359
params or kwargs,
316360
)
317361

318362
def delete_application(self, application_id):
363+
warnings.warn(
364+
"nexmo.Client#delete_application is deprecated (use methods from #application_v2 instead)",
365+
DeprecationWarning,
366+
stacklevel=2,
367+
)
319368
return self.delete(
320369
self.api_host,
321370
"/v1/applications/{application_id}".format(application_id=application_id),
@@ -447,6 +496,12 @@ def get(self, host, request_uri, params=None, header_auth=False):
447496
return self.parse(host, requests.get(uri, params=params, headers=headers))
448497

449498
def post(self, host, request_uri, params, header_auth=False):
499+
"""
500+
Post form-encoded data to `request_uri`.
501+
502+
Auth is either key/secret added to the post data, or basic auth,
503+
if `header_auth` is True.
504+
"""
450505
uri = "https://{host}{request_uri}".format(host=host, request_uri=request_uri)
451506
headers = self.headers
452507
if header_auth:
@@ -464,6 +519,9 @@ def post(self, host, request_uri, params, header_auth=False):
464519
return self.parse(host, requests.post(uri, data=params, headers=headers))
465520

466521
def _post_json(self, host, request_uri, json):
522+
"""
523+
Post json to `request_uri`, using basic auth.
524+
"""
467525
uri = "https://{host}{request_uri}".format(host=host, request_uri=request_uri)
468526
auth = base64.b64encode(
469527
(
@@ -480,12 +538,24 @@ def _post_json(self, host, request_uri, json):
480538
)
481539
return self.parse(host, requests.post(uri, headers=headers, json=json))
482540

483-
def put(self, host, request_uri, params):
541+
def put(self, host, request_uri, params, header_auth=False):
484542
uri = "https://{host}{request_uri}".format(host=host, request_uri=request_uri)
485543

486-
params = dict(params, api_key=self.api_key, api_secret=self.api_secret)
487-
logger.debug("PUT to %r with params %r", uri, params)
488-
return self.parse(host, requests.put(uri, json=params, headers=self.headers))
544+
headers = self.headers
545+
if header_auth:
546+
h = base64.b64encode(
547+
(
548+
"{api_key}:{api_secret}".format(
549+
api_key=self.api_key, api_secret=self.api_secret
550+
).encode("utf-8")
551+
)
552+
).decode("ascii")
553+
# Must create a new headers dict here, otherwise we'd be mutating `self.headers`:
554+
headers = dict(headers or {}, Authorization="Basic {hash}".format(hash=h))
555+
else:
556+
params = dict(params, api_key=self.api_key, api_secret=self.api_secret)
557+
logger.debug("PUT to %r with params %r, headers %r", uri, params, headers)
558+
return self.parse(host, requests.put(uri, json=params, headers=headers))
489559

490560
def delete(self, host, request_uri, header_auth=False):
491561
uri = "https://{host}{request_uri}".format(host=host, request_uri=request_uri)
@@ -500,6 +570,7 @@ def delete(self, host, request_uri, header_auth=False):
500570
).encode("utf-8")
501571
)
502572
).decode("ascii")
573+
# Must create a new headers dict here, otherwise we'd be mutating `self.headers`:
503574
headers = dict(headers or {}, Authorization="Basic {hash}".format(hash=h))
504575
else:
505576
params = {"api_key": self.api_key, "api_secret": self.api_secret}
@@ -600,19 +671,3 @@ def generate_application_jwt(self, when=None):
600671
payload.setdefault("jti", str(uuid4()))
601672

602673
return jwt.encode(payload, self.private_key, algorithm="RS256")
603-
604-
605-
def _format_date_param(params, key, format="%Y-%m-%d %H:%M:%S"):
606-
"""
607-
Utility function to convert datetime values to strings.
608-
609-
If the value is already a str, or is not in the dict, no change is made.
610-
611-
:param params: A `dict` of params that may contain a `datetime` value.
612-
:param key: The datetime value to be converted to a `str`
613-
:param format: The `strftime` format to be used to format the date. The default value is '%Y-%m-%d %H:%M:%S'
614-
"""
615-
if key in params:
616-
param = params[key]
617-
if hasattr(param, "strftime"):
618-
params[key] = param.strftime(format)

0 commit comments

Comments
 (0)