2

I wanted to adapt the wonderful Tutorial from Miguel Grinberg to create a unittest test-executor. I principally just adapted the code from Miguel to my needs, but I get a problem with generating the uri inside the fields map. As long as I remove the

'uri': fields.Url('test')

everything works fine, but otherwise I get a Build Error:

BuildError: ('test', {'Test_environment_id': 123, 'Test_duration': '0.5 sec', 'Success': 1, 'Failure_count': 0, 'Tested_files': 'Some files', 'Request_id': 1, 'Runs_count': 3, 'Created_on': '01.01.1970', 'Error_count': 0, 'Requester': 'John', 'Skipped_count': 2}, None)

I discovered very similar Question here on stackoverflow here but this did not help me to understand what is wrong with my code.I can use the described work-around from that question, but I would really like to know what am I doing wrong.

Here is my code:

#!/usr/bin/python
__author__ = 'karlitos'

from flask import Flask, jsonify, abort, make_response, request
from flask.ext.restful import Api, Resource, reqparse, fields, marshal,url_for
from time import strftime
from glob import glob
import os
import sqlite3

CURRENT_DIRECTORY = os.getcwd()
PROJECT_DIRECTORIES = glob('{}/projects/*'.format(CURRENT_DIRECTORY))
# create a sqlite database connection object
db_con = sqlite3.connect('{}unittest.db'.format(CURRENT_DIRECTORY))

app = Flask(__name__, static_url_path="")
api = Api(app)

tests = [
    {
        'Request_id': 1,
        'Requester': 'John',
        'Created_on': '01.01.1970',
        'Test_environment_id': 123,
        'Tested_files': 'Some files',
        'Test_duration': '0.5 sec',
        'Runs_count': 3,
        'Error_count': 0,
        'Failure_count': 0,
        'Skipped_count': 2,
        'Success': 1
    }
]

"""Structure storing the `Request_id`'s of all test currently running indexed by their `Test_environment_id`'s."""
env_id_of_running_tests = {}

"""Structure serving as a template for the `marshal` function which takes raw data and a dict of fields to output and
 filters the data based on those fields."""
test_fields = {
    'Request_id': fields.Integer,
    'Requester': fields.String,
    'Created_on': fields.String,
    'Test_environment_id': fields.Integer,
    'Tested_files': fields.String,
    'Test_duration': fields.String,
    'Runs_count': fields.Integer,
    'Error_count': fields.Integer,
    'Failure_count': fields.Integer,
    'Skipped_count': fields.Integer,
    'Success': fields.Boolean,
    'uri': fields.Url('test')
}

"""Validation function for the environment-id type which has to be in range [1,100]"""


def env_id_type(value, name):
    if value <= 1 or value >= 100:
        raise ValueError("The parameter '{}' is not between 1 and 100. The value: {} was provided".format(name, value))
    return value


class TestsAPI(Resource):
    def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument('Requester', type=str, required=True,
                                   help='No requester name provided', location='json')
        self.reqparse.add_argument('Test_environment_id', type=env_id_type, required=True,
                                   help='Bad environment-id provided, between 1 and 100.', location='json')
        super(TestsAPI, self).__init__()

    def get(self):
        return {'tests': [marshal(test, test_fields) for test in tests]}

    def post(self):
        args = self.reqparse.parse_args()

        request_id = tests[-1]['Request_id'] + 1

        # check if the current Test_environment_id is not under the currently running test
        if args['Test_environment_id'] in env_id_of_running_tests:
            return {'message': 'Another test with the same Environment-ID is still running.'}, 409
        else:
            env_id_of_running_tests[args['Test_environment_id']] = request_id
        test = {
            'Request_id': request_id,
            'Requester': args['Requester'],
            'Created_on': strftime('%a, %d %b %Y %H:%M:%S'),
            'Test_environment_id': args['Test_environment_id'],
            'Tested_files': 'Some files',
            'Test_duration': '',
            'Runs_count': None,
            'Error_count': None,
            'Failure_count': None,
            'Skipped_count': None,
            'Success': None
        }

        tests.append(test)

        return {'test started': marshal(test, test_fields)}, 201


class TestAPI(Resource):

    def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument('Request_id', type=int, required=True,
                                   help='No Request-ID provided', location='json')
        super(TestAPI, self).__init__()

    def get(self, request_id):
        test = [test for test in tests if test['Request_id'] == request_id]
        print 'Request_ID', request_id
        if len(test) == 0:
            abort(404)
        return {'test   ': marshal(test[0], test_fields)}


api.add_resource(TestsAPI, '/test-executor/api/tests', endpoint='tests')
api.add_resource(TestAPI, '/test-executor/api/tests/<int:request_id>', endpoint='test')

if __name__ == '__main__':
    app.run(debug=True)
Community
  • 1
  • 1
karlitos
  • 1,604
  • 3
  • 27
  • 59

2 Answers2

0

Your 'test' endpoint's route has a parameter request_id (with a lowercase initial r), but your test data dict has an entry with the key Request_id (upper case initial R). When marshalling the data, flask-restful looks for an entry with the lowercase request_id in the test data dictionary in order to construct the URL, but it can't find it because of the case mismatch.

If you change the parameter in the route to uppercase Request_id it'll take care of your BuildError for requests to the 'tests' endpoint. But with a request to the test endpoint, you'll then have new easily fixable errors due to similar case mismatches in TestAPI.get().

dblue
  • 161
  • 3
0

How to add fields url for nested output fields in flask restful helps answer the question.

You need to assign the 'test' in fields.Url('test') before returning it.

Happens mostly in POST and PUT

Community
  • 1
  • 1
AmaChefe
  • 395
  • 3
  • 8