Skip to content

Latest commit

 

History

History
308 lines (237 loc) · 9.34 KB

File metadata and controls

308 lines (237 loc) · 9.34 KB

Lab: Building a REST API in Python

Overview

In this lab, you'll learn the essentials of REST APIs, HTTP request methods, and how to build a simple API using Python's standard library. You'll also practice returning proper response codes and JSON data. The challenge will have you build a CRUD API using real networking data from psutil.


Part 1: What is a REST API?

A REST API (Representational State Transfer) is a way for programs to communicate over HTTP using standard methods and resource-based URLs. REST APIs are stateless and use standard HTTP verbs to perform actions on resources.


Part 2: HTTP Request Methods and Status Codes

Method Purpose
GET Retrieve data
POST Create new data
PUT Update/replace data
DELETE Remove data

Other methods: PATCH (partial update), OPTIONS (capabilities)

Lab: Building a REST API with Flask

Overview

In this lab, you'll learn how to build a REST API using Flask, a popular Python web framework. You'll focus on Flask module usage, reading parameters, returning JSON, and using Flask's documentation tools.


Part 1: Flask Basics and Documentation

  • Flask makes it easy to define routes, handle HTTP methods, and return JSON.
  • You can read the Flask docs with:
    • python -m pydoc flask
    • python -m pydoc flask.app
    • python -m pydoc flask.Flask
    • python -m pydoc flask.Flask.get
    • python -m pydoc flask.Flask.post
    • python -m pydoc flask.Flask.route
  • Flask's official docs: https://flask.palletsprojects.com/

Part 2: Example – In-Memory User API with Flask

This is not a real database! All data is lost when the server restarts.

"""
User API Example (Flask)

# Create a new user
curl -X POST -H "Content-Type: application/json" -d '{"username": "alice", "role": "user"}' http://localhost:8000/newuser

# Get user details by username
curl http://localhost:8000/users/alice

# Update username by id
curl -X PUT -H "Content-Type: application/json" -d '{"username": "alice2"}' http://localhost:8000/users/1

# Delete user by id
curl -X DELETE http://localhost:8000/users/1

# Get all users
curl http://localhost:8000/users

# Get user details by username (again)
curl http://localhost:8000/users/alice

# Note: Both GET endpoints above can also be accessed directly in your browser by navigating to:
#   http://localhost:8000/users
#   http://localhost:8000/users/alice
#   You can replace localhost with public ip
#   if port and firewall is open
"""

from flask import Flask, request, jsonify, abort

app = Flask(__name__)

# In-memory user database (lost on restart)
user_database = {
    1: {"id": 1, "username": "admin", "role": "admin"},
    2: {"id": 2, "username": "bob", "role": "user"}
}
next_id = 3

@app.route('/users/<username>', methods=['GET'])
def get_user(username):
    """Get user details by username"""
    for user in user_database.values():
        if user["username"] == username:
            return jsonify(user)
    return jsonify({"error": "User not found"}), 404

@app.get('/users')
def get_users():
    """Get all users"""
    return jsonify(user_database)

@app.route('/newuser', methods=['POST'])
def create_user():
    """Create a new user"""
    global next_id
    data = request.get_json()
    if not data or "username" not in data:
        return jsonify({"error": "Missing username"}), 400
    user = {"id": next_id, "username": data["username"], "role": data.get("role", "user")}
    user_database[next_id] = user
    next_id += 1
    return jsonify(user), 201

@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    """Update a user's username by id"""
    user = user_database.get(user_id)
    if not user:
        return jsonify({"error": "User not found"}), 404
    data = request.get_json()
    if not data or "username" not in data:
        return jsonify({"error": "Missing username"}), 400
    user["username"] = data["username"]
    return jsonify(user)

@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    """Delete a user by id (forbid deleting admin)"""
    user = user_database.get(user_id)
    if not user:
        return jsonify({"error": "User not found"}), 404
    if user["role"] == "admin":
        return jsonify({"error": "Cannot delete admin user"}), 403
    del user_database[user_id]
    return '', 204

@app.errorhandler(500)
def internal_error(error):
    return jsonify({"error": "Internal server error"}), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8000)

Part 3: Flask Module Nuances and Best Practices

Query Parameters

Use request.args to access query parameters:

@app.route('/search')
def search():
    term = request.args.get('q', '')
    return jsonify({'results': [], 'query': term})

Reading Headers

Access headers with request.headers:

@app.route('/whoami')
def whoami():
    agent = request.headers.get('User-Agent', 'unknown')
    return jsonify({'user_agent': agent})

Custom Response and Status

Use make_response() for advanced control:

from flask import make_response
@app.route('/custom')
def custom():
    resp = make_response(jsonify({'msg': 'hi'}), 202)
    resp.headers['X-Custom'] = 'value'
    return resp

Error Handling

Custom error handlers:

@app.errorhandler(404)
def not_found(e):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(400)
def bad_request(e):
    return jsonify({'error': 'Bad request'}), 400

Use abort() to raise errors:

from flask import abort
@app.route('/fail')
def fail():
    abort(400, description='You failed!')

Blueprints (for Modular Apps)

from flask import Blueprint
user_bp = Blueprint('user', __name__)

@user_bp.route('/users')
def list_users():
    return jsonify(list(user_database.values()))

app.register_blueprint(user_bp)

Before/After Request Hooks

@app.before_request
def log_request():
    print(f"{request.method} {request.path}")

@app.after_request
def add_header(response):
    response.headers['X-Processed-By'] = 'Flask'
    return response

Flask Config and Environment

app.config['MY_SETTING'] = 'value'
import os
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev')

Route Shortcuts & CORS

  • Use @app.get, @app.post, etc. for concise HTTP method routing.

  • @app.get is same as using @app.route(..., methods=['GET'])

  • For cross-origin requests (CORS), add:

    from flask_cors import CORS
    CORS(app)
  • CORS is needed when your frontend and backend run on different domains/ports, allowing browsers to access your API.

Flask CLI and Shell

  • Run with flask run (set FLASK_APP=yourfile.py)
  • Use flask shell for an interactive shell with app context

Flask Documentation


Challenge – Build Your Own Network API (Flask)

Goal: Build a REST API that exposes networking data (routes, interfaces, MAC addresses, etc.) using psutil. All data should be loaded into a dictionary and modified in-memory only.

Requirements:

  • Use psutil to gather networking data (e.g., interfaces, MACs, routes)
  • Store this data in a dictionary (in-memory)
  • Implement CRUD operations:
    • GET: Return current data
    • POST: Add new entry
    • PUT: Update entry
    • DELETE: Remove entry
  • Use Flask for all endpoints
  • Return proper status codes and JSON responses
  • Add a /search endpoint that takes query parameters and returns any dictionary entries (from any resource) where any key or value matches the search term (case-insensitive).
  • For every request, log the User-Agent and client IP address (use request.headers and request.remote_addr).
  • Use @app.before_request and @app.after_request to log the request method, path, and the time taken to complete each request (start timer in before, log duration in after).

Bonus:

  • Support response in JSON, XML, or YAML (based on Accept header or query param)

Tips:

  • Use request.get_json() to read request bodies (for POST/PUT)
  • Use request.args to read query parameters
  • Use jsonify() to return JSON

  • Use http.server (subclass BaseHTTPRequestHandler)
  • Return proper status codes and JSON responses

Bonus

  • Support response in JSON, XML, or YAML (based on Accept header or query param)

Example Endpoints

  • GET /interfaces → List all interfaces
  • GET /macs → List all MAC addresses
  • GET /routes → List all routes (simulate if needed)
  • POST /routes → Add a route (in-memory only)
  • PUT /routes/<id> → Update a route
  • DELETE /routes/<id> → Delete a route

Tips

  • Use self.rfile.read() to read request bodies (for POST/PUT)
  • Use json.loads() to parse JSON input
  • Use self.path to determine the endpoint
  • Use self.headers to check for Accept type (for bonus)

Bonus Exploration: FastAPI

If you finish early, try building a similar API using FastAPI. FastAPI is similar to Flask but uses async programming, so it can process multiple requests at the same time. You don’t need to learn all the details—just refer to the official docs and try making another implementation of your API with FastAPI for comparison.