Skip to content

Workshop: Python & APIs on IBM i

Requirements

You must Bring Your Own Device and have access to an IBM i.

Goals

  • Understand APIs & Documentation
  • Host an API locally with Python
  • Consume an API with Python
  • Learn the basics of Authentication

RESTful API Basics

REST (Representational State Transfer) is a way to design networked applications. It uses HTTP requests to perform operations like Create, Read, Update, and Delete (CRUD) on resources identified by URLs.

Key principles of REST:

  • Stateless: Each request from client to server must contain all the information needed to understand and process the request.
  • Client-Server: The client and server are independent and can be developed separately.
  • Cacheable: Responses must define themselves as cacheable or not to prevent clients from reusing stale or inappropriate data.
  • Uniform Interface: Resources are identified in the request, and the operations are defined by HTTP methods (GET, POST, PUT, DELETE, etc.).

In summary, RESTful APIs provide a standardized way for systems to communicate over HTTP using a set of well-defined operations and principles.

Methods

GET

The GET method retrieves data from the server.

PUT

The PUT method replaces all current representations of the target resource with the request payload.

PATCH

The PATCH method is used to apply partial modifications to a resource.

POST

The POST method is used update an existing resource or create a new resource.

DELETE

The DELETE method deletes the specified resource.

HEAD/OPTIONS/TRACE/CONNECT

Less commonly used methods for various purposes like checking the server status, options available, tracing the request, and establishing a tunnel.

Status Codes

200 OK

The request has succeeded.

201 Created

The request has been fulfilled and has resulted in one or more new resources being created.

400 Bad Request

The server cannot or will not process the request due to an apparent client close.

401 Unauthorized

Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.

404 Not Found

The requested resource could not be found but may be available in the future.

500 Internal Server Error

A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.

APIs Visualized

Swagger UI is a popular tool for visualizing APIs. It provides a user-friendly interface to interact with APIs and understand their endpoints and data structures.

If an API has an OpenAPI formatted yml or json file, you can use Swagger UI to visualize it.

API Playground Swagger

Setting up your Environment

Installing Python

Installing Python is straightforward. You can download the latest version from the official Python website.

When you first install Python, make sure to check the box that says [✓] Add Python to PATH!

Project Setup

Step #1

Create a new folder for your project. This can be a folder anywhere on your computer.

Step #2

Open Visual Studio Code and select FILE and “Open Folder” to open your project folder.

Step #3

Open the terminal (Ctrl + ` in VS Code).

Step #4

Create a new virtual environment:

VSCode Terminal
python3 -m venv --system-site-packages venv

Step #5

Activate your virtual environment:

  • On Windows (powershell):
    VSCode Terminal
    venv\Scripts\activate
  • On macOS/Linux or Windows (bash):
    VSCode Terminal
    source venv/bin/activate
  • On PASE (IBM i):
    VSCode Terminal
    . venv/bin/activate

Setting up a Basic Flask Application

Step 1: Use the python package manager pip to install Flask

VSCode Terminal
pip3 install flask

Step 2: Create a new file hello.py

hello.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Hello, World!'
# ^^ Add New Routes Above ^^
if __name__ == '__main__':
app.run(debug=True, port=5000)

We start by importing the Flask module and creating a new instance of the Flask class. We then define a route for the root URL / that returns the string “Hello, World!“.

Step 2: Run your Flask app

VSCode Terminal
python3 hello.py

Step 3: Open your browser

Navigate to http://127.0.0.1:5000. You should see “Hello, World!“.

Setting up the Database Connection

Step 1: Install additional packages

VSCode Terminal
pip3 install python-dotenv pyodbc

Step 2: Create a new file .env updating USERNAME and PASSWORD

.env
PORT=5000
DB_HOST=YOUR_IBMI
DB_ID=YOUR_USERNAME
DB_PASSWORD=YOUR_PASSWORD
DB_NAME=YOUR_DB_NAME

Step 3: Copy this to a new file db.py

db.py
import os
import pyodbc
from dotenv import load_dotenv
load_dotenv()
class Database:
def __init__(self):
self.user = os.getenv('DB_ID')
self.password = os.getenv('DB_PASSWORD')
self.database = os.getenv('DB_NAME')
def __enter__(self):
self.conn = pyodbc.connect(
f"DRIVER={{IBM i Access ODBC Driver}};SYSTEM={os.getenv('DB_HOST')};"
f"UID={os.getenv('DB_ID')};PWD={os.getenv('DB_PASSWORD')}",
autocommit=True
)
self.cursor = self.conn.cursor()
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.conn.commit()
self.conn.close()
def get_db():
return Database()

Creating API Endpoints

GET /employees

Create a new program host.py

Here’s an example of a simple API that returns a list of employees from the SAMPLE.EMPLOYEE table:

host.py
import os
from flask import Flask, jsonify, request
from db import get_db
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
@app.route('/employees', methods=['GET'])
def get_employees():
with get_db() as cursor:
cursor.execute("""
select
empno as "id",
firstnme as "first",
lastname as "last",
job as "job",
workdept as "workdept",
salary as "salary"
from sample.employee
""")
rows = cursor.fetchall()
employee_list = []
for row in rows:
employee_list.append({
"id": row.id,
"first": row.first,
"last": row.last,
"job": row.job,
"workdept": row.workdept,
"salary": row.salary
})
return jsonify(employee_list)
# ^^ Add New Routes Above ^^
if __name__ == '__main__':
app.run(debug=True, port=int(os.getenv('PORT')))

GET /employees/{id}

Add the following route to host.py to retrieve a specific employee by their ID:

host.py
@app.route('/employees/<id>', methods=['GET'])
def get_employee(id):
with get_db() as cursor:
cursor.execute("""
select
empno as "id",
firstnme as "first",
lastname as "last",
job as "job",
workdept as "workdept",
salary as "salary"
from sample.employee
where empno = ?
""", (id,))
employee = cursor.fetchone()
if employee:
employee_dict = {
"id": employee[0],
"first": employee[1],
"last": employee[2],
"job": employee[3],
"workdept": employee[4],
"salary": employee[5]
}
return jsonify(employee_dict)
else:
return jsonify({"error": "Employee not found"}), 404

GET /departments/

Add the following route to host.py to retrieve all departments:

host.py
@app.route('/departments', methods=['GET'])
def get_departments():
with get_db() as cursor:
cursor.execute("""
select
deptno as "id",
deptname as "name",
location as "location",
mgrno as "manager"
from sample.department
""")
rows = cursor.fetchall()
department_list = []
for row in rows:
department_list.append({
"id": row.id,
"name": row.name,
"location": row.location,
"manager": row.manager
})
return jsonify(department_list)

GET /departments/{id}

Add the following route to host.py to retrieve a specific department by its ID:

host.py
@app.route('/departments/<id>', methods=['GET'])
def get_department(id):
with get_db() as cursor:
cursor.execute("""
select
deptno as "id",
deptname as "name",
location as "location",
mgrno as "manager"
from sample.department
where deptno = ?
""", (id,))
department = cursor.fetchone()
if department:
department_dict = {
"id": department[0],
"name": department[1],
"location": department[2],
"mgrno": department[3]
}
return jsonify(department_dict)
else:
return jsonify({"error": "Department not found"}), 404

Testing your API

Before we move onto other REST methods, let’s cover how to test your API with tools that are available to you.

Thunder Client

Use the extension in VS Code to test your API endpoints.

  • Create a New Request and
  • Specify the Method (GET/PUT/PATCH) and URL (ex. http://127.0.0.1:5000/employees)
  • Add any headers or body data.
  • Click Send Request to see the response.

PATCH /departments/{id}

Add the following route to host.py to update the manager for a specific department by its ID using a PATCH request:

host.py
@app.route('/departments/<id>', methods=['PATCH'])
def update_department(id):
data = request.get_json()
with get_db() as cursor:
cursor.execute("""
SELECT
deptno as "id",
deptname as "name",
location as "location",
mgrno as "manager"
FROM sample.department
WHERE deptno = ?
""", (id,))
department = cursor.fetchone()
if department:
cursor.execute(
"""
UPDATE sample.department
SET deptname = ?, location = ?, mgrno = ?
WHERE deptno = ?
WITH NC
""",
(data['name'], data['location'], data['manager'], id)
)
updated_department = {
"id": id,
"name": data['name'],
"location": data['location'],
"manager": data['manager']
}
return jsonify(updated_department)
else:
return jsonify({"error": "Department not found"}), 404

Test this endpoint using Thunder Client!

Select PATCH and enter http://127.0.0.1:5000/departments/A00 with the following JSON data in the Request Body:

{
"name": "New Department Name",
"location": "New Location",
"manager": "000020"
}

Then switch to GET to verify the changes.

Consuming an API

To consume an API, you can use the popular requests library in Python. Install it using:

VSCode Terminal
pip3 install requests

GET /employees

First, create a new file consume.py and add the following code to retrieve the list of employees:

consume.py
import requests
# Get the list of employees
response = requests.get('http://127.0.0.1:5000/employees')
if response.status_code == 200:
employees = response.json()
print("Employees:", employees)
else:
print("Failed to retrieve employees:", response.status_code)
print(response.text)

This script sends a GET request to the /employees endpoint and prints the list of employees if the request is successful.

Testing consume.py

VSCode Terminal
python3 consume.py

PATCH /departments

Next, add code to consume.py to patch departments with data from the /departments endpoint:

consume.py
# Send the GET request to retrieve all departments
response = requests.get('http://127.0.0.1:5000/departments')
if response.status_code == 200:
departments = response.json()
for department in departments:
department_id = department['id']
updated_department = {
"name": department['name'],
"manager": department['manager'],
"location": "Virginia Beach"
}
# Send the PATCH request to update the department's location
patch_response = requests.patch(f'http://127.0.0.1:5000/departments/{department_id}', json=updated_department)
if patch_response.status_code == 200:
print(f"Department {department_id} location updated successfully.")
else:
print(f"Failed to update department {department_id}:", patch_response.status_code)
print(patch_response.text)
else:
print("Failed to retrieve departments:", response.status_code)
print(response.text)

This script sends a PATCH request to the /departments endpoint with the new location data and prints the ID of the updated departments if the request is successful.

Basics of Authentication

Common Types

  • Basic Authentication: This method involves sending the username and password encoded in Base64 in the Authorization header.
  • Bearer Token: This method uses a token that is generated by the server and sent in the Authorization header. The token is usually a JWT (JSON Web Token).
  • API Key: This method involves sending a unique key, usually in the query parameters or headers, to authenticate requests.

Testing Auth Tokens

To test Auth Tokens add the Authorization header to your requests.

If your client supports it, there might be a dedicated tab for Auth. Refer to the screenshot for using a bearer token in Thunder Client.

Thunder Client Auth Token

Sending a Bearer Token in your GET request to /employees

To send a bearer token in your GET request to /employees, you need to include the Authorization header with the token when calling requests.

consume.py
import requests
# Define the bearer token
token = 'your_token_here'
# Set the headers with the Authorization token
headers = { 'Authorization': f'Bearer {token}' }
# Get the list of employees
response = requests.get('http://127.0.0.1:5000/employees', headers=headers)
if response.status_code == 200:
employees = response.json()
print("Employees:", employees)
else:
print("Failed to retrieve employees:", response.status_code)

Replace ‘your_token_here’ with your actual bearer token. This script sends a GET request to the /employees endpoint with the bearer token included in the Authorization header.

Generating a Auth Token with API Playground

API Playground Valid Token

Send a HTTP request to /employees or /departments with the Auth Token and watch it populate on the Receive Tab

API Playground Valid Token

Using the Receive Tab click the Token button to view the validity of the token

API Playground Valid Token

Congratulations! 🎉

You’ve successfully set up a basic Flask application, created API endpoints, consumed those APIs, and learned about authentication.

This is for you!