REST Methods
via Provisioning Portal API

Now that you have had some experience interacting with SOAP-based APIs, you can turn your attention to the other type of web service: REST. In order to send REST-based requests using Python, this lab will utilize the Python Requests library. To make this re-usable, a REST class will be implemented as a basic building block to send and receive the various request types (GET, POST, PUT, and DELETE) and to manage session re-use, so that each REST API, such as CUPI and CMS, can extend the class by adding the API-specific headers and parsing of the responses.

While you won't be able to test this code individually here, all subsequent REST modules will leverage it.

  1. Basic REST Request
  2. Response Checking

Step 1 - Basic REST Request

The REST class will use the following parameters:

  • host: The host name / IP address of the server
  • port: The server port for API access (default: 443)
  • username and password: The credentials for basic authentication
  • base_url: The base URL, such as "/api/v1" for a given API. This is simply to avoid having to send, for example, "/api/v1" with every request.
  • headers: A dictionary of header settings that we need to apply. These are specific to the API you are interacting with, such as a specific Accept header for CUPI.
  • tls_verify: Whether or not server certificate validation will be enforced.

Within this class, besides the __init__ method to initialize the class, there will be two methods:

  • _send_request() send the request to the server--regardless of what verb is required and return the response. This method should return a Python dictionary indicating 'success', the raw 'response', and a 'message', which can be used to relay error information which may have occurred.
  • _check_response() used to check if the response was in the 200-299 range, indicating a success from the HTTP perspective. This will only help identify basic HTTP-type errors. More API-specific error evaluation will occur later. For example, CUPI may return a 400 HTTP response to a query and then put more information about the specific error in the payload, which we can further process and return to the user.

Start implementing these methods using Visual Studio Code.

  1. In your VS Code tab, open up flaskr/rest/v1/rest.py using the Explorer
  2. The __init__ method initializes the REST object with parameters mentioned above. In order for your application to minimize its impact on the server, add a Session() instance. This object, also part of the Python Requests library, allows certain parameters to persist from one request to the next (meaning, that it adds and re-uses a session ID in the request header, if the server supplied one). The server, therefore, does not need to instantiate a new client session for every request made, potentially using up limited server resources--especially in the case of Unity Connection and Communications Manager--as their individual sessions do not time out for 30 minutes, by default. Notice that the Session() instance is created with credentials and the TLS verification setting. The headers are also updated with the API-specific headers that are passed to the object.

    Add the following to the __init__() method:

    
        def __init__(self, host, username=None, password=None, base_url=None, headers={}, port=443, tls_verify=False):
            """
            Initialize an object with the host, port, and base_url using the parameters passed in.
            """
            self.host = host
            self.port = str(port)
            self.base_url = base_url
    
            self.session = Session()
            self.session.auth = HTTPBasicAuth(username, password)
            self.session.verify = tls_verify
            self.session.headers.update(headers)
            

  3. Currently the _send_request method only sets a result variable. You add in functionality by first properly assigning the url variable using the settings obtained from when the class was initialized (in __init__). This is, in effect, just a concatenation of the strings using the host, port, base_url and api_method in the correct place to form a correct URL (as we had used with Postman).

    Insert the highlighted line below to assign the url variable:

    
            # Set the URL using the parameters of the object and the api_method requested
            # in a format such as: https//host:port/api/v1/api_method
            url = "https://{}:{}{}/{}".format(self.host, self.port, self.base_url, api_method)
            

  4. Now add the code to send the individual request types. As we noted with Postman, the POST and PUT often have both parameters and a payload, while GET can only have parameters and DELETE will not have anything aside from the URL. These requests should be placed inside a try block so that if a RequestExcept exception occurs, it can be returned in the 'message' instead of crashing your program. There will be a different request for each of the supported types. Only PUT and POST will need to send a payload, and a DELETE doesn't require any parameters other than the URL to send to, which will include and object ID of some kind built into the URL. If a response is received, you simply place it in 'response' and set 'success' to True. You do not need to do any further interpretation of whether things were successful yet.

    Insert the following below the # Send the request comment line:

    
            # Send the request and handle RequestException that may occur
            try:
                if http_method in ['GET', 'POST', 'PUT', 'DELETE']:
                    if http_method == 'GET':
                        raw_response = self.session.get(url, params=parameters)
                    elif http_method == 'POST':
                        raw_response = self.session.post(url, data=payload, params=parameters)
                    elif http_method == 'PUT':
                        raw_response = self.session.put(url, data=payload, params=parameters)
                    elif http_method == 'DELETE':
                        raw_response = self.session.delete(url)
    
                    result = {
                        'success': True,
                        'response': raw_response,
                        'message': 'Successful {} request to: {}'.format(http_method, url)
                    }
                    
            except RequestException as e:
                result = {
                    'success': False,
                    'message': 'RequestException {} request to: {} :: {}'.format(http_method, url, e)}
            return result        

Step 2 - Response Checking

In most cases you will want to have some checks in place to see if a HTTP response code other than the 200-299 range was received. This can indicate congestion or other transient or permanent failure on the server side. You just need a method that you can call to check for that simple condition. In this lab, you won't implement any retries or other handling, but it could be added for specific cases.

  1. In flaskr/rest/v1/rest.py, we have created a method called _check_response. Currently it looks like this. Remove the highlighted pass line:

    
        def _check_response(self, raw_response):
            """
            Evaluates a response status.  If it has a non-2XX value, then set the 'success' result
            to false and place the exception in the 'message'
    
            :param raw_response: The raw response from a _send_request. 
    
            :returns: Returns a the same dictionary passed to it, with the 'success' and 'message'
                      keys modified, if needed.
            :rtype:  Dict 
            """
            pass
            

  2. Add the following code:

    
        def _check_response(self, raw_response):
            """
            Evaluates a response status.  If it has a non-2XX value, then set the 'success' result
            to false and place the exception in the 'message'
    
            :param raw_response: The raw response from a _send_request. 
    
            :returns: Returns a the same dictionary passed to it, with the 'success' and 'message'
                      keys modified, if needed.
            :rtype:  Dict 
            """
            try:
                # Raise HTTPError error for non-2XX responses
                raw_response['response'].raise_for_status()
            except HTTPError as e:
                raw_response['success'] = False
                raw_response['message'] = 'HTTPError Exception: {}'.format(e)
            return raw_response
            

  3. Save this file now, as you have finished configured it.

Now you have a method to send the requests and one to check the results for obvious return failures. Next you can implement each individual API to utilize these methods and parse the results according to their own requirements.