Creating Python APIs

Creating Python APIs

REST is an incredibly powerful solution for web APIs in the modern space. It offers a wide array of benefits that can help any service be more efficient, faster to iterate, and more stable.

Python is a strong, high-level language that unlocks a high level of functionality across broad categories of systems and devices. It is human-readable, highly efficient, and widely adopted.

These two technologies, when combined, can deliver an incredible product in the API space. In this guide, we’re going to talk through what REST is, how to make something truly RESTful, and how Python supports this effort. We’ll look at some best practices

What is REST?

REST, or Representational State Transfer, is a paradigm for systems communication. The concept is to make systems communicate with one another through a stateless modality that utilizes representations of resource states rather than the resources themselves. In essence, every interaction is with a stateful representation utilizing a specific relationship between the resource requester and resource holder, which allows for seamless interaction across a wide variety of use cases.

Basics of REST

REST as a concept was first shared in a now-famous dissertation from Roy Fielding. In this dissertation, Fielding defined a new modality of communication that was based on representational state transfer. While there’s a variety of optional attributes that make something “RESTful” in practice, the core nature of REST is best defined as follows:

  • REST utilizes a ’’Client-Server relationship’’. This means that the client and server have their own domains and states and that the server interacts with the client (and vice-versa) to provide functionality. To put it another way, in REST, there is only one modality of communication – ‘’’the client requests from the server, and the server responds to the client’’’’.

  • REST is ‘’’stateless’’’. “State” is the current attributes and nature of a given resource. In a stateless environment, the state is managed entirely on the client side, and a request to a server should contain all of the information that is needed to process and return a response. In essence, ‘’’REST utilizes statelessness to make every request self-contained’’’.

  • REST has a ‘’’uniform interface’’’. Every component has a pre-defined and consistent interface, meaning that interaction with components ‘’’must utilize the same modality and construct’’’ regardless of the form or function within a service. Resources must have a single URI, and should, where possible, reference additional data or information – this is HATEOAS/hypermedia, and is a major part of making something RESTful.

  • REST must be ‘’’cacheable’’’. In REST, servers mark data as either being something that can be cached or something that cannot be cached, which has a significant impact on performance, server demands, and user experience. ‘’’REST must support cache choice’’’. Data that should be cacheable must be cacheable and must be declared as such.

  • REST is layered. RESTful components allow for distributed architecture, and a client typically won’t know whether it is directly connected to the server or to one of the microservices and middleware systems in the architecture.

What Makes a RESTful API RESTful?

There are a lot of opinions about what makes something truly RESTful, but one good metric for this consideration is the Richardson Maturity Model. This model, developed by Leonard Richardson, allows us to establish a rubric for judging when something is truly RESTful or only partially so.

The maturity model breaks things into four levels.

Level 0: The Swamp of POX

This is the lowest level of the model, and indicates an API with a URI which accepts all inputs. Level 0 suggests a system that is an API in name without any of the mechanisms or verbiage for requests and responses that make a system RESTful – for this reason, ‘’’Level 0 is Non-RESTful’’’.

Level 1: Resources

At Level 1, resources are defined, allowing users to make requests of a resource URI rather than a service endpoint. In essence, instead of asking a function to do something, Level 1 APIs call a method of a particular resource to do something. For this reason, ‘’’Level 1 is Approaching REST’’’.

Level 2: HTTP verbs

Level 2 sees an incredibly important addition in the form of HTTP verbs. In REST, HTTP verbs have a specific form and function, but in lower levels they can be used more generally. For instance, in Level 1, it’s not uncommon for both POST and GET to function identically to one another, even though they have very different purposes by definition. Because REST requires caching, and HTTP only supports caching with proper implementation of its verbiage, ‘’’Level 2 is Approaching REST with Caching’’’.

Level 3: Hypermedia controls

Finally, at Level 3, we see the introduction of hypermedia with HATEOAS, or Hypertext as the Engine of Application State. HATEOAS allows for contextual additions to resources, providing additional information, linked resources, etc. This is a huge element of RESTful design that is often the missing link in services that declare themselves as RESTful without being truly RESTful. ‘’’Level 3 is Likely RESTful’’’.

What is “Likely RESTful”?

An important note here is that our definitions above of “Approaching REST with Caching” and so forth is an approximation of what each level means, and is not itself a definition from the maturity model. So with that said, why is the final level, Level 3, only “Likely RESTful”?

Simply put, RESTful has a few guidelines that are absolutely necessary, but it also has a few needs that can be a bit more flexible. Roy Fielding, the inventor of REST, has detailed at length just what is required to be RESTful, and he rightfully pointed out that many APIs who proclaim themselves to be RESTful are not actually so.

For this reason, it’s possible to be in Level 3 with some idiosyncrasies that undermine the definition of being RESTful. In such a case, your service is likely RESTful, but may not meet all the requirements in full detail. This is why it is so important to read through the entirety of the REST dissertation and its resultant qualities if you wish to be truly RESTful.

What is Python?

With a solid understanding of REST, let’s look at Python. Python is a programming language that was first released in 1991 as a successor language to ABC. The intention of the language was to be human-readable and efficient, with an emphasis on garbage collection, dynamic typing, and multi-paradigm support. It packages together a wide range of libraries to enable a huge amount of functions and purposes, and for this reason, it’s a very popular choice for web APIs.

Python is popular because it’s quite easy to get started. It utilizes simple syntax and is highly readable, leveraging normal human speech and whitespace to make things clearer and more approachable. It abstracts a lot of complex underlying functions into simple structures, allowing you to get started very quickly with low friction.

Notably, Python has high cross-platform support across a variety of systems and environments. The community is exceptionally strong, meaning that in those environments without native Python support, there’s likely a community extension or system that allows for this functionality and integration.

How to Create a RESTful API with Python

For the purposes of this piece, we’re going to use Python with Flask. There are a variety of excellent tools for Python API development, but we are using Flask as it does not require any tools or libraries to start building your web API. Flask does have extensions to add additional features, but for this API, we’re going to use the toolset straight out of the box.

Setting Up Your Development Environment

First things first – this tutorial assumes you already have Python installed. If you do not, navigate to the Python webpage and install via the method that suits your environment‘’’cacheable’’’.

Installing Flask

With Python installed, you must first install Flask. To do so, issue the following command:

pip install flask

This will install Flask from the Python Package Index.

Creating the Environment

Next, you need to create a directory for your Python API. We will call this directory “Moesif_tutorial:

mkdir moesif_tutorial && cd moesif_tutorial

This code will make the directory (mkdir) and change the working directory (cd) to the newly created location. From here, you need to create your new project file. We will call this “tutorial_app”.

touch tutorial_app.py

By “touching” the file, we are creating our build file and getting it ready to hold our project. “touch” will create the file, but we’ll need to launch a virtual environment to work on it. To do this, use the following command:

python3 -m venv <venv>/bin/activate

This command will depend on your environment directory – for more information on how to properly start this directory, refer to the Python documentation.

Create the API Skeleton

With our environment created, we’re going to craft our API. This API is quite simple – it’s going to contain details about three Python frameworks and allow users to retrieve information about these frameworks.

To get started building the API, we will directly edit tutorial_app.py. Within the file, add the following code:

from flask import Flask
app = Flask(__name__)

What this does is instantiate the Flask class, in effect “creating” the application. With this, we next created an instance of the class with the name value, which is where we’ll point Flask at various files and elements.

This is the most basic version of a Python application – but to make it a RESTful version, we need to query an endpoint, and in our case, this means we need some data for it to reference. Since we don’t have a database connected, we can store this data directly as a data store. In Python, a data store is a piece of arbitrary code that we can store within the app itself, which allows us to host data without a database or external data storage solution.

To do this, we can append the data store to our file:

from flask import Flask
app = Flask(__name__)

in_memory_datastore = {
"Flask" : {"name": "Flask", "created": 2010, "parent": "Python"},
"Django" : {"name": "Django", "created": 2005, "parent": "Python"},
"Bottle" : {"name": "Bottle", "created": 2009, "parent": "Python"},
"Tornado" : {"name": "Tornado", "created": 2010, "parent": "Python"},
}

Create the Endpoints

Now you have the skeleton of an API – it has an instantiated application and a resource. Next, we need to create the endpoint which can be queried. To do this, we will use the HTTP verb GET to retrieve data from tutorial_app.py:

@app.get('/frameworks')
def list_frameworks():
   return {"frameworks":list(in_memory_datastore.values())}

There are a few things here to mention. Firstly, the area ‘/frameworks’ defines our endpoint. Requests using the GET HTTP verb to that endpoint will trigger this functionality. For other endpoints, you can simply change this name and generate a new endpoint. Secondly, @app.get is a shorthand for another way of doing this. Some Python developers may be more familiar with “@app.route” – in this case, app.get and app.route are one in the same, just built slightly different.

Our method looks like this:

@app.get('/frameworks')

Another way to write this would be as follows:

@app.route('/frameworks', methods=["GET"])

Note that both pieces of code do the same thing – our version is just shorter. Back to the code as written:

@app.get('/frameworks')
def list_frameworks():
   return {"frameworks":list(in_memory_datastore.values())}

The rest of the code defines a JSON object with the label “frameworks” and points it to the values in our data store. This will allow us to access the /frameworks endpoint and see a list of all resources. To do so, you can navigate to:

http://127.0.0.1:5000/frameworks

Implement CRUD Functions

So far, we have an API that allows us to retrieve a list of data. What we need to do is start adding additional CRUD functions. For instance, we can create an update function utilizing PUT. To do this, we can update our code to add the POST verb to create new entries to our data store. First, we will need to make the create function:

def create_framework(new_framework):
framework_name = new_framework['name']
in_memory_datastore[framework_name] = new_framework
return new_framework

This defines a new way to enter a framework into the system. Next, we need to add a route method to support CLI commands to interact with the endpoint:

@app.route('/frameworks'’, methods=['GET', 'POST'])
def frameworks_route():
   if request.method == 'GET':
       return list_frameworks()
   elif request.method == "POST":
       return create_framework(request.get_json(force=True))

This can be repeated for all of the routes needed for CRUD functionality.

Make it RESTful

Now that you have an API service that provides basic functionality, you’re going to need to start adding some systems to truly make it RESTful. The first step here is to utilize an effective HATEOAS contextual system.

There are a variety of options currently on the market. One good solution is JSON-LD, which allows you to provide linked data as part of a resource.

An example of how this might look in our instance is as follows:

{
  "@context": "https://example.com/frameworks.jsonld",
  "@id": "https://example.com/frameworks/Flask",
  "relatedName": "Flask-RESTPlus",
  "relatedDocs": "https://flask-restplus.readthedocs.io/",
}

This is also the point you may want to start considering additional frameworks for caching, optimization, etc. Once you have a steady API from which to work off, you can start adding additional secure and stable features to make your application truly RESTful.

Best Practices

Authentication and Authorization

Ensure that you are deploying your service with authentication and authorization that is based on the principle of least privilege. Keep access to resources as locked down as possible. Ensure that your contextual references are valid and that they are worth making – hypermedia is great, but exposing anything and everything is not the right response to a more closed off, non-contextual world. Make sure you are providing useful data that can’t be used against you, and ensure that data is provided in a secure way.

API Structure

Remember that RESTful APIs are principally resource-oriented. Resources should have a single HTTP verb that has a specific function, and should be idempotent (e.g., the response is the same type each time it’s invoked, even if the contents are different). Ensure you are adequately linking to contextual information and are providing caching to clients. Ensure you are aligned against the REST considerations at the start of this piece.

Additional Reading

Reference documentation as much as you can – this has been done before, and well, so learn from others! Some good links include:

Conclusion

RESTful design is not hard to do, but it’s often hard to do right. With some forward thinking and planning, you can use Python to launch an incredibly powerful RESTful API. Even just the code in this piece can be the central bones of the next big API – it just takes some imagination and planning.

Detailed API Analytics with Moesif Detailed API Analytics with Moesif

Detailed API Analytics with Moesif

Learn More