DELETE: the Final Verb
Our driver is very nearly complete. We only have one verb left to go, so let's get to it!
Test
As it would happen, delete
has the same signature and expectations as get
, so let's go ahead and copypasta test_get
and modify it to test_delete
in maury/tests/test_client.py
:
@requests_mock.Mocker()
def test_delete(self, m):
# The happy path
m.delete('https://api.engineyard.com/sausages', text='{"sausages":"gold"}')
c = Client(token = 'faketoken')
result = c.delete('sausages')
self.assertTrue(result.ok)
self.assertEqual(result.body, {'sausages' : 'gold'})
# The happy path with params
m.delete(
'https://api.engineyard.com/sausages?color=gold',
text='{"sausages":"yep"}')
result = c.delete('sausages', params = {'color' : 'gold'})
self.assertTrue(result.ok)
self.assertEqual(result.body, {'sausages' : 'yep'})
# A wild API error appears!
m.delete(
'https://api.engineyard.com/ed209',
status_code = 500,
text = 'Drop your weapon. You have 20 seconds to comply.')
result = c.delete('ed209')
self.assertFalse(result.ok)
self.assertFalse(result.error == None)
# PEBCAK
m.delete(
'https://api.engineyard.com/404',
status_code = 404,
text = 'You are now staring into the void. It is staring back.')
result = c.delete('404')
self.assertFalse(result.ok)
self.assertFalse(result.error == None)
Okay, we have a failing test now, so we're going to do the obvious.
Implementing DELETE
Since the signature is the same as get
, we're going to copypasta the get
method definition and modify it to fit our new delete
method in maury/client.py
:
def delete(self, path, params = None):
"""Perform an HTTP DELETE on the API.
Given an endpoint path and a dictionary of parameters, send the request
to the API and return the result.
Positional arguments:
path -- the path of the API endpoint you wish to DELETE
Keyword arguments:
params -- a dictionary of query params (default: None)
"""
return self.__make_request('DELETE', path, params = params)
There we have it. Now that all of the defined requirements are met and all of our tests pass, our client driver is technically complete.
The Completed Driver
For the sake of being able to see everything all at once, here is the full source for the complete driver:
from furl import furl
import requests
from .result import Result
class Client(object):
"""A base driver that talks to the Engine Yard API"""
def __init__(self, base_url = 'https://api.engineyard.com', token = None):
"""Instantiate a new Client instance
Keyword arguments:
base_url -- the base URL of the API (default: 'https://api.engineyard.com')
token -- the API authentication token (default: None)
"""
self.__base_url = base_url
self.__headers = {
'X-EY-Token' : token,
'accept' : 'application/vnd.engineyard.com.v3+json',
'content-type' : 'application/json'
}
def __construct_request_url(self, path):
"""Construct a URL for an API endpoint.
Given a relative endpoint path, construct a fully-qualified API URL.
"""
# Get a URL object that we can edit
u = furl(self.__base_url)
# Set the path to the endpoint in question
u.path = path
# Return the modified URL
return u.url
def __process_response(self, response):
"""Process an API response into a Result."""
if response.ok:
return Result(response.json(), None)
return Result(
None,
"The API returned the following status: %d" % response.status_code
)
def __make_request(self, verb, path, params = None, data = None):
"""Send an HTTP request to the server.
Given an HTTP verb, an endpoint path, a dictionary of parameters,
and a dictionary of body data, send the request to the API and
return the result.
Positional arguments:
verb -- the HTTP verb to use for the request
path -- the path of the API endpoint you wish to address
Keyword arguments:
params -- a dictionary of query params (default: None)
data -- a dictionary of POST data (default: None)
"""
response = requests.request(
# Uppercase the verb, just in case
verb.upper(),
self.__construct_request_url(path),
params = params,
json = data,
headers = self.__headers,
allow_redirects = True
)
return self.__process_response(response)
def get(self, path, params = None):
"""Perform an HTTP GET on the API.
Given an endpoint path and a dictionary of parameters, send the request
to the API and return the result.
Positional arguments:
path -- the path of the API endpoint you wish to GET
Keyword arguments:
params -- a dictionary of query params (default: None)
"""
return self.__make_request('GET', path, params = params)
def post(self, path, params = None, data = None):
"""Perform an HTTP POST on the API.
Given an endpoint path, a dictionary of parameters, and a dictionary of
POST data, send the request to the API and return the result.
Positional arguments:
path -- the path of the API endpoint you wish to POST
Keyword arguments:
params -- a dictionary of query params (default: None)
data -- a dictionary of POST data (default: None)
"""
return self.__make_request('POST', path, params = params, data = data)
def put(self, path, params = None, data = None):
"""Perform an HTTP PUT on the API.
Given an endpoint path, a dictionary of parameters, and a dictionary of
PUT data, send the request to the API and return the result.
Positional arguments:
path -- the path of the API endpoint you wish to PUT
Keyword arguments:
params -- a dictionary of query params (default: None)
data -- a dictionary of PUT data (default: None)
"""
return self.__make_request('PUT', path, params = params, data = data)
def patch(self, path, params = None, data = None):
"""Perform an HTTP PATCH on the API.
Given an endpoint path, a dictionary of parameters, and a dictionary of
PATCH data, send the request to the API and return the result.
Positional arguments:
path -- the path of the API endpoint you wish to PATCH
Keyword arguments:
params -- a dictionary of query params (default: None)
data -- a dictionary of PATCH data (default: None)
"""
return self.__make_request('PATCH', path, params = params, data = data)
def delete(self, path, params = None):
"""Perform an HTTP DELETE on the API.
Given an endpoint path and a dictionary of parameters, send the request
to the API and return the result.
Positional arguments:
path -- the path of the API endpoint you wish to DELETE
Keyword arguments:
params -- a dictionary of query params (default: None)
"""
return self.__make_request('DELETE', path, params = params)