Typorithm · medium

Validate relationships and preserve list error locations

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

Step 1 of 4 · intro card

Validate relationships and preserve list error locations

0%

Concept and contract

Check cross-field invariants and current many=True error behavior.

Individually valid fields can form an invalid object. A low value greater than its high value violates an interval contract even when both numbers are nonnegative. Object-level validate receives the checked field values and can enforce that relationship.

The fixture validates a list of intervals with many=True. It also accepts a floating score so the current rejection of NaN and infinity is visible. DRF 3.18 changed list error reporting to a dictionary keyed by failed item position; the lab uses the current behavior instead of teaching an obsolete list shape.

Mechanism and worked example

The serializer first validates field types and bounds, then runs the interval invariant.

The serializer first validates field types and bounds, then runs the interval invariant. A failure that concerns the combination is attached to non_field_errors. Errors inside a submitted list retain the item index, allowing a client to identify which item needs correction.

The helper converts ErrorDetail objects to their stable codes and stringifies index keys for a JSON-compatible public result. It does not flatten every failure into the first item or return the entire submitted payload. Good error reporting preserves location while limiting unnecessary data exposure.

Complete worked example

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Interval(serializers.Serializer):
    low = serializers.IntegerField(min_value=0)
    high = serializers.IntegerField(min_value=0)
    score = serializers.FloatField(default=0.0)
    def validate(self, values):
        if values["low"] > values["high"]:
            raise serializers.ValidationError("Bounds are not ordered")
        return values

def codes(value):
    if isinstance(value, dict):
        return {str(key): codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [codes(item) for item in value]
    return value.code

def inspect_example(payload):
    serializer = Interval(data=payload, many=True)
    if serializer.is_valid():
        return {"value": [dict(item) for item in serializer.validated_data]}
    return {"errors": codes(serializer.errors)}
Reasoning
The serializer first validates field types and bounds, then runs the interval invariant. A failure that concerns the combination is attached to non_field_errors. Errors inside a submitted list retain the item index, allowing a client to identify which item needs correction. The helper converts ErrorDetail objects to their stable codes and stringifies index keys for a JSON-compatible public result. It does not flatten every failure into the first item or return the entire submitted payload. Good error reporting preserves location while limiting unnecessary data exposure.

Focused decision: context is in the complete example

python

def validate(self, values):
    if values["low"] > values["high"]:
        raise serializers.ValidationError("Bounds are not ordered")
    return values
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 omits the low/high relationship check.

The counterexample omits the low/high relationship check. A reversed interval then passes despite satisfying each individual field bound. Separate cases reject non-finite score text under the patched DRF field implementation. A framework upgrade and an application invariant protect different things.

Do not assume schema examples enforce a relationship or that a database constraint replaces useful request validation. Also avoid losing item positions when adapting a new library error format. Version-sensitive clients should have regression tests for both rejection and error location.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Interval(serializers.Serializer):
    low = serializers.IntegerField(min_value=0)
    high = serializers.IntegerField(min_value=0)
    score = serializers.FloatField(default=0.0)
    def validate(self, values):
        if False:
            raise serializers.ValidationError("Bounds are not ordered")
        return values

def codes(value):
    if isinstance(value, dict):
        return {str(key): codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [codes(item) for item in value]
    return value.code

def inspect_example(payload):
    serializer = Interval(data=payload, many=True)
    if serializer.is_valid():
        return {"value": [dict(item) for item in serializer.validated_data]}
    return {"errors": codes(serializer.errors)}
Reasoning
The counterexample omits the low/high relationship check. A reversed interval then passes despite satisfying each individual field bound. Separate cases reject non-finite score text under the patched DRF field implementation. A framework upgrade and an application invariant protect different things. Do not assume schema examples enforce a relationship or that a database constraint replaces useful request validation. Also avoid losing item positions when adapting a new library error format. Version-sensitive clients should have regression tests for both rejection and error location.

Independent practice

Make the interval strictly increasing by rejecting equality as well as reversal.

Make the interval strictly increasing by rejecting equality as well as reversal. Keep the finite-score policy and dictionary error locations. This small change requires modifying the comparison rather than rewriting list validation or weakening field constraints.

Test an equal interval, an increasing interval and a non-finite score. Explain whether a zero-width interval is valid for the actual domain before selecting the stricter rule in a product. The exercise intentionally changes that one contract.

Step 1: Reject zero-width intervals without weakening finite-score validation or list error locations. 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()

from rest_framework import serializers

class Interval(serializers.Serializer):
    low = serializers.IntegerField(min_value=0)
    high = serializers.IntegerField(min_value=0)
    score = serializers.FloatField(default=0.0)
    def validate(self, values):
        if values["low"] > values["high"]:
            raise serializers.ValidationError("Bounds are not ordered")
        return values

def codes(value):
    if isinstance(value, dict):
        return {str(key): codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [codes(item) for item in value]
    return value.code

def inspect_example(payload):
    serializer = Interval(data=payload, many=True)
    if serializer.is_valid():
        return {"value": [dict(item) for item in serializer.validated_data]}
    return {"errors": codes(serializer.errors)}

# Independent task: Reject zero-width intervals without weakening finite-score validation or list error locations.
Reasoning
Reject zero-width intervals without weakening finite-score validation or list error locations.
  • 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 changes the relationship from low > high to low >= high.

The solution changes the relationship from low > high to low >= high. Equality now produces the same object-level invalid code at the failed list index. An increasing interval remains valid, and Infinity is still rejected at score.

A collection may additionally need duplicate identity or cross-item ordering rules. Those belong to a deliberate list-level policy, not an accidental side effect of individual field validation. Keep error contracts bounded and test representative list positions when extending this fixture.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Interval(serializers.Serializer):
    low = serializers.IntegerField(min_value=0)
    high = serializers.IntegerField(min_value=0)
    score = serializers.FloatField(default=0.0)
    def validate(self, values):
        if values["low"] >= values["high"]:
            raise serializers.ValidationError("Bounds are not ordered")
        return values

def codes(value):
    if isinstance(value, dict):
        return {str(key): codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [codes(item) for item in value]
    return value.code

def inspect_example(payload):
    serializer = Interval(data=payload, many=True)
    if serializer.is_valid():
        return {"value": [dict(item) for item in serializer.validated_data]}
    return {"errors": codes(serializer.errors)}
Reasoning
The solution changes the relationship from low > high to low >= high. Equality now produces the same object-level invalid code at the failed list index. An increasing interval remains valid, and Infinity is still rejected at score. A collection may additionally need duplicate identity or cross-item ordering rules. Those belong to a deliberate list-level policy, not an accidental side effect of individual field validation. Keep error contracts bounded and test representative list positions when extending this fixture.

Interview reasoning and next step

Explain the sequence of field and object validation, then show how one failed list index survives into the JSON-compatible error representation.

Explain the sequence of field and object validation, then show how one failed list index survives into the JSON-compatible error representation. Name the current-release behavior that an older client adapter would need to handle.

Next, apply validation to partial updates. Missing input during PATCH is different from a missing required field during creation. A validated patch also needs a clear policy for fields controlled by the server.

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

Check cross-field invariants and current many=True error behavior.