Typorithm · easy

Flask: response contracts and expected errors

pythonflaskindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Flask: response contracts and expected errors

0%

Concept and contract

Return truthful statuses and consistent JSON for expected missing resources.

A response contract includes its status, media type and body. An error-looking JSON object with status 200 still tells clients that the request succeeded.

Flask permits returning a dictionary and status tuple, and error handlers can adapt expected HTTP errors. Those handlers should preserve meaning rather than turn every failure into an indistinguishable success envelope.

Mechanism and worked example

The route exposes one fictional role and aborts with 404 for any other identifier.

The route exposes one fictional role and aborts with 404 for any other identifier. A dedicated handler produces a small JSON error response for both an absent object and an unmatched path.

The helper inspects status, decoded JSON and MIME type through the real test client. These observations explain the whole client-visible contract without opening an external HTTP connection.

Complete worked example

python

from flask import Flask, abort
app = Flask(__name__)
app.config['TESTING'] = True
@app.errorhandler(404)
def not_found(error):
    return {'error': 'not found'}, 404
@app.get('/roles/<int:role_id>')
def detail(role_id):
    if role_id != 1:
        abort(404)
    return {'id': 1}

def response_for(path):
    response = app.test_client().get(path)
    return {'status': response.status_code, 'body': response.json,
            'mime': response.mimetype}
Reasoning
The route exposes one fictional role and aborts with 404 for any other identifier. A dedicated handler produces a small JSON error response for both an absent object and an unmatched path.

Counterexamples and common mistakes

The counterexample omits the status in the handler return, so Flask supplies 200.

The counterexample omits the status in the handler return, so Flask supplies 200. Its body still looks like an error, which is why checking only a message is insufficient.

Avoid a blanket Exception handler that labels programming failures as missing data. Unexpected errors need appropriate server diagnostics, while public responses should not leak tracebacks, credentials or internal query details.

Counterexample: a runnable framework mistake

python

from flask import Flask, abort
app = Flask(__name__)
app.config['TESTING'] = True
@app.errorhandler(404)
def not_found(error):
    return {'error': 'not found'}
@app.get('/roles/<int:role_id>')
def detail(role_id):
    if role_id != 1:
        abort(404)
    return {'id': 1}

def response_for(path):
    response = app.test_client().get(path)
    return {'status': response.status_code, 'body': response.json,
            'mime': response.mimetype}
Reasoning
The counterexample omits the status in the handler return, so Flask supplies 200. Its body still looks like an error, which is why checking only a message is insufficient.

Independent practice: one bounded framework change

Add a stable machine-readable code ROLE_NOT_FOUND to the 404 body while preserving its message, status and JSON media type.

Add a stable machine-readable code ROLE_NOT_FOUND to the 404 body while preserving its message, status and JSON media type. Leave the successful response unchanged.

This small task separates a machine contract from display wording. Do not expose the raw exception string as the code, because that can change across library versions or reveal implementation details.

Step 1: Add ROLE_NOT_FOUND as the stable code for JSON 404 responses. Step 2: Change the dedicated handler body.

Practice starter: solve before reading the solution

python

from flask import Flask, abort
app = Flask(__name__)
app.config['TESTING'] = True
@app.errorhandler(404)
def not_found(error):
    return {'error': 'not found'}, 404
@app.get('/roles/<int:role_id>')
def detail(role_id):
    if role_id != 1:
        abort(404)
    return {'id': 1}

def response_for(path):
    # Remove this placeholder, then adapt the retained baseline:
    # Add ROLE_NOT_FOUND as the stable code for JSON 404 responses.
    raise NotImplementedError
    response = app.test_client().get(path)
    return {'status': response.status_code, 'body': response.json,
            'mime': response.mimetype}
Reasoning
Add ROLE_NOT_FOUND as the stable code for JSON 404 responses. Change the dedicated handler body.
  • Hint 1: Keep the explicit 404 status.
  • Hint 2: Leave successful resource JSON unchanged.

Solution, tests and trade-offs

The fixtures request the known identifier, a missing identifier and an unknown route.

The fixtures request the known identifier, a missing identifier and an unknown route. Practice checks the same three cases with the extra code only on failures.

A solution that changes every response or forgets the status will fail. Manually request a disallowed method afterward and observe that its default handler is a separate concern, rather than assuming this 404 handler covers everything.

Practice solution and acceptance checks

python

from flask import Flask, abort
app = Flask(__name__)
app.config['TESTING'] = True
@app.errorhandler(404)
def not_found(error):
    return {'error': 'not found', 'code': 'ROLE_NOT_FOUND'}, 404
@app.get('/roles/<int:role_id>')
def detail(role_id):
    if role_id != 1:
        abort(404)
    return {'id': 1}

def response_for(path):
    response = app.test_client().get(path)
    return {'status': response.status_code, 'body': response.json,
            'mime': response.mimetype}
Reasoning
The fixtures request the known identifier, a missing identifier and an unknown route. Practice checks the same three cases with the extra code only on failures.

Interview reasoning and next step

As an API grows, document a bounded error vocabulary and decide which failures are safe for clients to retry.

As an API grows, document a bounded error vocabulary and decide which failures are safe for clients to retry. Include a correlation identifier when needed, but keep confidential request content out of public error bodies.

Response consistency does not replace operational monitoring or transaction guarantees. It simply makes each layer communicate its result honestly and gives tests precise evidence to compare.

Optional stretch — after the core task works: Define separate safe contracts for validation and rate-limit errors.

Computer practice: https://applydjinn.com/typorithm/python-frameworks-lab-20261105.zip. Install the pinned dependencies in an isolated environment, run verify.py --track flask, then edit the retained exercise baseline and check it with --practice fl-errors. The lab uses owned in-memory databases, local framework clients and tiny CPU tensors. It never reads the working ApplyDjinn database, contacts production feeds or downloads models.

Return truthful statuses and consistent JSON for expected missing resources.