Building REST API Endpoints with Flask and Writing Automated Tests

User Management API Implementation

This article demonstrates how to implement CRUD operations for a user management module using Flask. The examples focus on comon patterns encountered in production systems.

Environment: Python 3.6, PostgreSQL 10, Flask 0.12

Create User Endpoint

Route Definition

@app.route('/guardian/users', methods=['POST'])
@require_role('guardian_admin')
@login_required
def create_user():
    """
    Expected JSON payload:
    {
        "user": {
            "domain_id": "default",
            "enabled": True,
            "name": "testuser",
            "password": "secret123",
            "realname": "John Doe",
            "email": "john@example.com",
            "tel": "+86-029-88880000",
            "phone": "13800138000",
            "org_id": "7d22d25a-b648-4ad3-a834-2398eb989a9e",
            "org_array": [],
            "comments": "User notes"
        }
    }
    """
    try:
        domain_id = None
        user_name = None
        user_passwd = None
        project_id = None
        real_name = None
        email = None
        phone = None
        org_id = None
        org_array = None
        comments = None
        
        req_obj = request.data
        req_json = req_obj.decode() if isinstance(req_obj, bytes) else req_obj
        user_obj = jsonpickle.loads(req_json)
        
        if 'default_project_id' in user_obj['user']:
            project_id = user_obj['user']['default_project_id']
        
        domain_id = user_obj['user']['domain_id']
        user_name = user_obj['user']['name']
        user_passwd = user_obj['user']['password']
        
    except Exception as ee:
        abort(400)
    
    if domain_id and user_name and user_passwd:
        resp = usermgt.create_user(domain_id, user_name, user_passwd, project_id)
        if 'id' in resp['user']:
            try:
                user_id = resp['user']['id']
                real_name = user_obj['user'].get('realname')
                email = user_obj['user'].get('email')
                tel = user_obj['user'].get('tel')
                phone = user_obj['user'].get('phone')
                org_id = user_obj['user'].get('org_id')
                org_array = user_obj['user'].get('org_array')
                comments = user_obj['user'].get('comments')
                
                GuardianModelDao._save_update_users_info(
                    user_id, user_name, real_name, email, tel, phone, 
                    org_id, org_array, comments
                )
                return wrap_response(SUCCESS_MSG, 200)
            except Exception as e:
                abort(500)
        else:
            abort(500)

Database Layer

@classmethod
def _save_update_users_info(cls, user_id, username, real_name, email, tel, phone, org_id, org_array, comments):
    LOG.debug('start create users info...')
    try:
        with TxConn() as conn:
            with conn.cursor() as cursor:
                now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                users_data = {
                    "user_id": user_id,
                    "username": username,
                    "realname": real_name,
                    "email": email,
                    "tel": tel,
                    "phone": phone,
                    "org": org_id,
                    "org_array": org_array,
                    "comments": comments,
                    "now_time": now_time
                }
                cursor.execute(cls.insert_users_info, users_data)
            conn.commit()
    except Exception as e:
        LOG.exception('error saving users info', e)
        raise e

Test Scrript for Create User

import unittest
import requests

class GuardianTestCase(unittest.TestCase):
    guardian_base = 'http://127.0.0.1:7070'
    user_token = keystone.user_token('default', 'admin', 'password')
    
    def test_create_user_api(self):
        myheaders = {
            'Content-Type': 'application/json',
            'X-Subject-Token': self.user_token
        }
        
        user_payload = {
            "user": {
                "domain_id": "default",
                "enabled": True,
                "name": "testuser1",
                "password": "secret123",
                "realname": "John Doe",
                "email": "john@example.com",
                "tel": "+86-029-88880000",
                "phone": "13800138000",
                "org_id": "7d22d25a-b648-4ad3-a834-2398eb989a9e",
                "org_array": None,
                "comments": "Test user"
            }
        }
        
        response = requests.post(
            self.guardian_base + '/guardian/users',
            json=user_payload,
            headers=myheaders,
            verify=True
        )
        
        if response.ok:
            self.assertIsNotNone(response.content)
            LOG.info('create users, response: {0}'.format(response.content))
        else:
            LOG.error('error create users, response: {0}, status: {1}'.format(
                response.content, response.status_code))

Update User Endpoint

The update endpoint uses PATCH method with user_id as a path parameter:

@app.route('/guardian/users/<string:user_id>', methods=['PATCH'])
@require_role('guardian_admin')
@login_required
def update_user(user_id):
    try:
        resp = usermgt.update_user(domain_id, user_id, username, user_passwd, project_id, enabled)
        if resp is not None:
            GuardianModelDao._save_update_users_info(
                user_id, username, real_name, email, tel, phone, 
                org_id, org_array, comments
            )
            return wrap_response(SUCCESS_MSG, 200)
    except Exception as e:
        LOG.exception('error modify user', e)
        abort(500)

Test Script for Udpate User

def test_update_user_api(self):
    myheaders = {
        'Content-Type': 'application/json',
        'X-Subject-Token': self.user_token
    }
    
    user_payload = {
        "user": {
            "domain_id": "default",
            "enabled": True,
            "name": "testuser1",
            "password": "newpass123",
            "realname": "Jane Doe",
            "email": "jane@example.com",
            "tel": "+86-029-88880000",
            "phone": "13800138000",
            "org_id": "7d22d25a-b648-4ad3-a834-2398eb989a9e",
            "comments": "Updated user"
        }
    }
    
    response = requests.patch(
        self.guardian_base + '/guardian/users/0d26176579e94d558c4c2d864b003241',
        json=user_payload,
        headers=myheaders,
        verify=True
    )
    
    if response.ok:
        self.assertIsNotNone(response.content)
        LOG.info('update users, response: {0}'.format(response.content))
    else:
        LOG.error('error update users, response: {0}, status: {1}'.format(
            response.content, response.status_code))

Query User Endpoint

@app.route('/guardian/users/<string:user_name>', methods=['GET'])
@require_role('guardian_admin')
@login_required
def query_user_by_username(user_name):
    try:
        user_info = GuardianModelDao._query_users_info_by_name(user_name)
        if len(user_info):
            return wrap_response(jsonpickle.dumps(user_info), 200)
        else:
            return wrap_response("[]", 200)
    except Exception as e:
        LOG.exception('error query user by name', e)
        abort(500)

Database Query Layer

@classmethod
def _query_users_info_by_id(cls, user_id):
    LOG.debug('start list users info...')
    users = []
    with TxConn() as conn:
        try:
            with conn.cursor() as cursor:
                data = (user_id,)
                cursor.execute(cls.query_users_info_by_id, data)
                for (user_id, real_name, email, tel, phone, org_id, org_array, 
                     comments, create_time, update_time) in cursor:
                    user = Users(user_id, real_name, email, tel, phone, org_id, 
                                org_array, comments, create_time, update_time)
                    users.append(user)
        except Exception as e:
            LOG.exception('error query users !')
            raise e
    return users

Test Script for Query User

def test_query_user_api(self):
    myheaders = {
        'Content-Type': 'application/json',
        'X-Subject-Token': self.user_token
    }
    
    response = requests.get(
        self.guardian_base + '/guardian/users/testuser',
        headers=myheaders,
        verify=True
    )
    
    if response.ok:
        self.assertIsNotNone(response.text)
        LOG.info('query users, response: {0}'.format(response.text))
    else:
        LOG.error('error query users, response: {0}, status: {1}'.format(
            response.text, response.status_code))

Delete User Endpoint

@app.route('/guardian/users/<string:user_id>', methods=['DELETE'])
@require_role('guardian_admin')
@login_required
def delete_user(user_id):
    if user_id is None:
        abort(400)
    try:
        status_code = usermgt.delete_user(user_id)
        if status_code == 204:
            return wrap_response(SUCCESS_MSG, status=status_code)
    except Exception as e:
        LOG.exception('error delete users info', e)
        abort(500)

Backend Delete Logic

def delete_user(user_id):
    myheaders = {
        'Content-Type': 'application/json',
        'X-Auth-Token': settings.KEYSTONE_ADMIN_TOKEN
    }
    response = requests.delete(
        settings.KEYSTONE_BASE + '/v3/users/' + user_id,
        headers=myheaders,
        verify=False
    )
    
    if response.status_code == 204:
        LOG.debug('delete users -- response {0}'.format(response))
        return response.status_code
    else:
        LOG.error('error list users, response: {0}'.format(response))
        raise KeystoneException(
            'error delete user {0} with response status {1}'.format(
                user_id, response.status_code), 
            response.status_code
        )

Test Script for Delete User

def test_delete_users_api(self):
    myheaders = {
        'Content-Type': 'application/json',
        'X-Subject-Token': self.user_token
    }
    
    response = requests.delete(
        self.guardian_base + '/guardian/users/testuser1',
        headers=myheaders,
        verify=True
    )
    
    if response.ok:
        self.assertIsNotNone(response.content)
        LOG.info('delete users api, response: {0}'.format(response.content))
    else:
        LOG.error('error delete users api, response: {0}, status: {1}'.format(
            response.content, response.status_code))

API Documentation Summary

Operation URL Method Body
Create User /guardian/users POST JSON with user object
Update User /guardian/users/<user_id> PATCH JSON with user object
Query User /guardian/users/<user_name> GET None
Delete User /guardian/users/<user_id> DELETE None

Create User Request Body:

{
    "user": {
        "domain_id": "default",
        "enabled": true,
        "name": "testuser",
        "password": "secret123",
        "realname": "John Doe",
        "email": "john@example.com",
        "tel": "+86-029-88880000",
        "phone": "13800138000",
        "org_id": "7d22d25a-b648-4ad3-a834-2398eb989a9e",
        "org_array": null,
        "comments": "User notes"
    }
}

Update User Request Body:

{
    "user": {
        "domain_id": "default",
        "enabled": true,
        "name": "testuser",
        "password": "newsecret",
        "realname": "Jane Doe",
        "email": "jane@example.com",
        "tel": "+86-029-88880000",
        "phone": "13800138000",
        "org_id": "7d22d25a-b648-4ad3-a834-2398eb989a9e",
        "org_array": null,
        "comments": "Updated notes"
    }
}

Tags: Flask REST API python Automated Testing HTTP Methods

Posted on Mon, 26 Oct 2026 16:28:47 +0000 by sk8erh4x0r