Typorithm · easy

Flask: JSON boundaries, validation and request limits

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: JSON boundaries, validation and request limits

0%

Concept and contract

Separate media-type errors, malformed JSON, shape validation and bounded input.

An incoming request can fail at several different boundaries. Its media type may not describe JSON, its bytes may be malformed, or its decoded value may have the wrong shape.

A valid JSON array is still not the object this endpoint accepts. Flask 3.1 also provides explicit request resource controls; a body limit should be chosen before reading unbounded content.

Mechanism and worked example

This endpoint accepts a short string title inside an object, trims surrounding whitespace and returns the cleaned title with status 201.

This endpoint accepts a short string title inside an object, trims surrounding whitespace and returns the cleaned title with status 201. get_json keeps its standard media-type and parse behavior.

Expected client errors are translated locally, while MAX_CONTENT_LENGTH limits the owned fixture to 128 bytes. A production limit needs to account for encoded bytes and infrastructure limits, not just character count.

Complete worked example

python

from flask import Flask, request
from werkzeug.exceptions import BadRequest, UnsupportedMediaType
app = Flask(__name__)
app.config.update(TESTING=True, MAX_CONTENT_LENGTH=128)
@app.post('/roles')
def create():
    try:
        body = request.get_json()
    except (BadRequest, UnsupportedMediaType) as error:
        return {'error': 'json required'}, error.code
    if not isinstance(body, dict) or type(body.get('title')) is not str:
        return {'error': 'title required'}, 400
    title = body['title'].strip()
    if not title or len(title) > 20:
        return {'error': 'title invalid'}, 400
    return {'title': title}, 201

def submit(raw, content_type='application/json'):
    response = app.test_client().post('/roles', data=raw, content_type=content_type)
    return {'status': response.status_code, 'body': response.json}
Reasoning
This endpoint accepts a short string title inside an object, trims surrounding whitespace and returns the cleaned title with status 201. get_json keeps its standard media-type and parse behavior.

Counterexamples and common mistakes

The counterexample accepts an empty cleaned title, so whitespace-only input creates a meaningless record..

The counterexample accepts an empty cleaned title, so whitespace-only input creates a meaningless record.

Do not use force=True to make every content type appear to be JSON, and avoid silent=True when a parse error must be distinguished from legitimate null. Broadly catching Exception and returning 400 can also conceal a programming or database failure that belongs on the server side.

Counterexample: a runnable framework mistake

python

from flask import Flask, request
from werkzeug.exceptions import BadRequest, UnsupportedMediaType
app = Flask(__name__)
app.config.update(TESTING=True, MAX_CONTENT_LENGTH=128)
@app.post('/roles')
def create():
    try:
        body = request.get_json()
    except (BadRequest, UnsupportedMediaType) as error:
        return {'error': 'json required'}, error.code
    if not isinstance(body, dict) or type(body.get('title')) is not str:
        return {'error': 'title required'}, 400
    title = body['title'].strip()
    if len(title) > 20:
        return {'error': 'title invalid'}, 400
    return {'title': title}, 201

def submit(raw, content_type='application/json'):
    response = app.test_client().post('/roles', data=raw, content_type=content_type)
    return {'status': response.status_code, 'body': response.json}
Reasoning
The counterexample accepts an empty cleaned title, so whitespace-only input creates a meaningless record.

Independent practice: one bounded framework change

Add a small integer years field constrained to the inclusive range zero through forty.

Add a small integer years field constrained to the inclusive range zero through forty. Reject booleans explicitly because bool is a subclass of int in Python.

Return the validated years beside title. Preserve media-type behavior, title cleanup and the byte limit. This adds one field policy rather than requiring a new serializer framework or a complete schema language.

Step 1: Validate integer years from 0 through 40 and return the cleaned value. Step 2: Read years from the parsed object.

Practice starter: solve before reading the solution

python

from flask import Flask, request
from werkzeug.exceptions import BadRequest, UnsupportedMediaType
app = Flask(__name__)
app.config.update(TESTING=True, MAX_CONTENT_LENGTH=128)
@app.post('/roles')
def create():
    try:
        body = request.get_json()
    except (BadRequest, UnsupportedMediaType) as error:
        return {'error': 'json required'}, error.code
    if not isinstance(body, dict) or type(body.get('title')) is not str:
        return {'error': 'title required'}, 400
    title = body['title'].strip()
    if not title or len(title) > 20:
        return {'error': 'title invalid'}, 400
    return {'title': title}, 201

def submit(raw, content_type='application/json'):
    # Remove this placeholder, then adapt the retained baseline:
    # Validate integer years from 0 through 40 and return the cleaned value.
    raise NotImplementedError
    response = app.test_client().post('/roles', data=raw, content_type=content_type)
    return {'status': response.status_code, 'body': response.json}
Reasoning
Validate integer years from 0 through 40 and return the cleaned value. Read years from the parsed object.
  • Hint 1: Use type(value) is int to reject bool.
  • Hint 2: Keep all earlier validation and statuses.

Solution, tests and trade-offs

The baseline checks a cleaned valid title, whitespace-only text and an array.

The baseline checks a cleaned valid title, whitespace-only text and an array. Practice adds boundary years values and a boolean rejection.

Run additional malformed JSON and wrong-media-type requests manually to observe distinct statuses. The framework reads only synthetic client bytes; no external source or production document is uploaded, and returning a response alone does not prove durable persistence.

Practice solution and acceptance checks

python

from flask import Flask, request
from werkzeug.exceptions import BadRequest, UnsupportedMediaType
app = Flask(__name__)
app.config.update(TESTING=True, MAX_CONTENT_LENGTH=128)
@app.post('/roles')
def create():
    try:
        body = request.get_json()
    except (BadRequest, UnsupportedMediaType) as error:
        return {'error': 'json required'}, error.code
    if not isinstance(body, dict) or type(body.get('title')) is not str:
        return {'error': 'title required'}, 400
    title = body['title'].strip()
    if not title or len(title) > 20:
        return {'error': 'title invalid'}, 400
    years = body.get('years')
    if type(years) is not int or not 0 <= years <= 40:
        return {'error': 'years invalid'}, 400
    return {'title': title, 'years': years}, 201

def submit(raw, content_type='application/json'):
    response = app.test_client().post('/roles', data=raw, content_type=content_type)
    return {'status': response.status_code, 'body': response.json}
Reasoning
The baseline checks a cleaned valid title, whitespace-only text and an array. Practice adds boundary years values and a boolean rejection.

Interview reasoning and next step

When a schema grows, centralize validation and error formatting instead of scattering inconsistent checks across views.

When a schema grows, centralize validation and error formatting instead of scattering inconsistent checks across views. Decide how unknown fields, null and omitted values differ before adding partial updates.

Flask supplies request and response primitives, while extensions or application code supply many API conventions. A later Django REST Framework or FastAPI course explores different validation tools without changing these fundamental boundaries.

Optional stretch — after the core task works: Add explicit tests for oversized and malformed request bodies.

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-json. 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.

Separate media-type errors, malformed JSON, shape validation and bounded input.