Typorithm · medium

Negotiate representations and keep failures explicit

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

Step 1 of 4 · intro card

Negotiate representations and keep failures explicit

0%

Concept and contract

Test supported media, malformed encodings and method dispatch on the patched framework.

Content negotiation selects a response representation the client can accept, while a parser handles the submitted representation. Accept and Content-Type therefore have different roles. A successful JSON parse does not mean that a client requesting only an unsupported response type should receive an invented success.

The fixture supports JSON input and JSON output, validates a small positive slots value and reports ordinary DRF failures through stable codes. It also tests a non-text charset declaration against current security-patched parsing. Inputs are tiny owned strings, not a stress test against a server.

Mechanism and worked example

The request factory supplies Accept and Content-Type independently.

The request factory supplies Accept and Content-Type independently. A supported JSON request reaches the serializer. A client accepting only text/html gets a negotiation failure because the declared renderer is JSON only. An unsupported non-text encoding is rejected instead of being treated as a normal text decoder.

The response is rendered before the helper reports its public result. This exercises the framework boundary rather than calling a serializer or handler directly. The helper limits reported error data and never returns a raw exception trace or complete submitted request as a convenience.

Complete worked example

python

from drf_lab import setup
setup()

import json
from rest_framework import serializers
from rest_framework.decorators import api_view, parser_classes, renderer_classes
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory

class Input(serializers.Serializer):
    slots = serializers.IntegerField(min_value=1, max_value=3)

@api_view(["POST"])
@parser_classes([JSONParser])
@renderer_classes([JSONRenderer])
def endpoint(request):
    if request.method == "GET":
        return Response({"ready": True})
    serializer = Input(data=request.data)
    serializer.is_valid(raise_exception=True)
    return Response(dict(serializer.validated_data))

def inspect_example(payload):
    request = APIRequestFactory().generic(payload.get("method", "POST"), "/accept/",
        data=json.dumps(payload.get("body", {"slots": 1})),
        content_type=payload.get("type", "application/json"),
        HTTP_ACCEPT=payload.get("accept", "application/json"))
    response = endpoint(request)
    body = {"error": response.data["detail"].code if "detail" in response.data else "validation_error"} if response.status_code >= 400 else dict(response.data)
    response.render()
    return {"status": response.status_code, "body": body}
Reasoning
The request factory supplies Accept and Content-Type independently. A supported JSON request reaches the serializer. A client accepting only text/html gets a negotiation failure because the declared renderer is JSON only. An unsupported non-text encoding is rejected instead of being treated as a normal text decoder. The response is rendered before the helper reports its public result. This exercises the framework boundary rather than calling a serializer or handler directly. The helper limits reported error data and never returns a raw exception trace or complete submitted request as a convenience.

Focused decision: context is in the complete example

python

@api_view(["POST"])
@parser_classes([JSONParser])
@renderer_classes([JSONRenderer])
def endpoint(request):
    serializer = Input(data=request.data)
    serializer.is_valid(raise_exception=True)
    return Response(dict(serializer.validated_data))
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample overwrites the requested Accept value with application/json.

The counterexample overwrites the requested Accept value with application/json. The unsupported-representation case then succeeds under a different request than the caller described. Tests can become falsely reassuring when their helper silently repairs invalid input.

Current DRF releases hardened large Accept-header processing and non-text charset handling. Keep a patched dependency, but do not infer that a small regression fixture proves resistance to every oversized request. Request-size limits, infrastructure admission and realistic measurements remain separate responsibilities.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

import json
from rest_framework import serializers
from rest_framework.decorators import api_view, parser_classes, renderer_classes
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory

class Input(serializers.Serializer):
    slots = serializers.IntegerField(min_value=1, max_value=3)

@api_view(["POST"])
@parser_classes([JSONParser])
@renderer_classes([JSONRenderer])
def endpoint(request):
    if request.method == "GET":
        return Response({"ready": True})
    serializer = Input(data=request.data)
    serializer.is_valid(raise_exception=True)
    return Response(dict(serializer.validated_data))

def inspect_example(payload):
    request = APIRequestFactory().generic(payload.get("method", "POST"), "/accept/",
        data=json.dumps(payload.get("body", {"slots": 1})),
        content_type=payload.get("type", "application/json"),
        HTTP_ACCEPT="application/json")
    response = endpoint(request)
    body = {"error": response.data["detail"].code if "detail" in response.data else "validation_error"} if response.status_code >= 400 else dict(response.data)
    response.render()
    return {"status": response.status_code, "body": body}
Reasoning
The counterexample overwrites the requested Accept value with application/json. The unsupported-representation case then succeeds under a different request than the caller described. Tests can become falsely reassuring when their helper silently repairs invalid input. Current DRF releases hardened large Accept-header processing and non-text charset handling. Keep a patched dependency, but do not infer that a small regression fixture proves resistance to every oversized request. Request-size limits, infrastructure admission and realistic measurements remain separate responsibilities.

Independent practice

Allow a safe GET readiness response in addition to POST.

Allow a safe GET readiness response in addition to POST. The handler already contains the small GET branch, but the decorator currently rejects that method. Change the declared method list while retaining content negotiation and validation for POST.

Check GET with JSON acceptance, POST with valid JSON and GET requesting unsupported HTML. The last should still fail negotiation. A Python branch alone does not register a public method, and a new method must not create a side path around the declared renderer policy.

Step 1: Register the existing safe GET branch while preserving renderer negotiation and POST validation. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from drf_lab import setup
setup()

import json
from rest_framework import serializers
from rest_framework.decorators import api_view, parser_classes, renderer_classes
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory

class Input(serializers.Serializer):
    slots = serializers.IntegerField(min_value=1, max_value=3)

@api_view(["POST"])
@parser_classes([JSONParser])
@renderer_classes([JSONRenderer])
def endpoint(request):
    if request.method == "GET":
        return Response({"ready": True})
    serializer = Input(data=request.data)
    serializer.is_valid(raise_exception=True)
    return Response(dict(serializer.validated_data))

def inspect_example(payload):
    request = APIRequestFactory().generic(payload.get("method", "POST"), "/accept/",
        data=json.dumps(payload.get("body", {"slots": 1})),
        content_type=payload.get("type", "application/json"),
        HTTP_ACCEPT=payload.get("accept", "application/json"))
    response = endpoint(request)
    body = {"error": response.data["detail"].code if "detail" in response.data else "validation_error"} if response.status_code >= 400 else dict(response.data)
    response.render()
    return {"status": response.status_code, "body": body}

# Independent task: Register the existing safe GET branch while preserving renderer negotiation and POST validation.
Reasoning
Register the existing safe GET branch while preserving renderer negotiation and POST validation.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution adds GET to api_view.

The solution adds GET to api_view. Supported reads return the small readiness object, existing writes preserve their validated slots response and unsupported Accept values remain rejected. No background work, durable state or production readiness claim is added by the new status object.

A product may support multiple renderers, but each representation needs suitable privacy and error handling. Returning a prebuilt response or bypassing the serializer can create a different path from the documented contract. Review actual emitted data in addition to generated schema.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

import json
from rest_framework import serializers
from rest_framework.decorators import api_view, parser_classes, renderer_classes
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory

class Input(serializers.Serializer):
    slots = serializers.IntegerField(min_value=1, max_value=3)

@api_view(["POST", "GET"])
@parser_classes([JSONParser])
@renderer_classes([JSONRenderer])
def endpoint(request):
    if request.method == "GET":
        return Response({"ready": True})
    serializer = Input(data=request.data)
    serializer.is_valid(raise_exception=True)
    return Response(dict(serializer.validated_data))

def inspect_example(payload):
    request = APIRequestFactory().generic(payload.get("method", "POST"), "/accept/",
        data=json.dumps(payload.get("body", {"slots": 1})),
        content_type=payload.get("type", "application/json"),
        HTTP_ACCEPT=payload.get("accept", "application/json"))
    response = endpoint(request)
    body = {"error": response.data["detail"].code if "detail" in response.data else "validation_error"} if response.status_code >= 400 else dict(response.data)
    response.render()
    return {"status": response.status_code, "body": body}
Reasoning
The solution adds GET to api_view. Supported reads return the small readiness object, existing writes preserve their validated slots response and unsupported Accept values remain rejected. No background work, durable state or production readiness claim is added by the new status object. A product may support multiple renderers, but each representation needs suitable privacy and error handling. Returning a prebuilt response or bypassing the serializer can create a different path from the documented contract. Review actual emitted data in addition to generated schema.

Interview reasoning and next step

Explain why an Accept failure is not a JSON parse failure and why a non-text charset declaration is a separate rejected boundary.

Explain why an Accept failure is not a JSON parse failure and why a non-text charset declaration is a separate rejected boundary. Then identify the helper mutation that made the counterexample accept an unsupported representation.

Next, generate and validate OpenAPI alongside actual API responses. Documentation should agree with the implementation, but it cannot sanitize a response or implement a permission check by itself.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/drf-lab-20261105.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice drf-negotiation. The real DRF clients, serializers, schemas and SQLite transactions use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Test supported media, malformed encodings and method dispatch on the patched framework.