Data validation in a Python API means checking every incoming value against explicit rules before your code uses it, and returning a clear error when a value fails. Pydantic does this through typed models, so you describe the shape of valid data once and get parsing, constraints and structured error messages for free. The graded exercises below take you from a simple model to custom validators and error formatting, with an answer key that explains each choice.
Why validation belongs at the edge of your API
An API receives text from strangers: browsers, mobile apps, scripts and sometimes attackers. If you trust that text, bugs show up deep inside your code as a KeyError, a TypeError or a corrupted database row. If you validate at the boundary, bad data is rejected immediately and the client learns exactly what to fix.
Three jobs are involved, and it helps to keep them separate:
- Parsing: turning raw JSON into Python types (for example, the string
"25"into the integer 25 if you allow that). - Validating: enforcing rules such as range, length, format and allowed values.
- Reporting: telling the caller which field failed, why, and with what status code.
Pydantic handles the first two and gives you structured data for the third. FastAPI, which is built on Pydantic, turns validation failures into HTTP 422 responses automatically. The exercises assume Pydantic v2 (BaseModel, Field, field_validator, model_validator). Syntax differs in v1, so check your installed version with pip show pydantic.
Setup for the Pydantic exercise set
You need Python 3.9 or newer, a virtual environment and two packages. Install them with pip install pydantic fastapi uvicorn. For Levels 1 to 3 you only need a plain Python file and Pydantic. Add FastAPI from Level 4 onward.
Work in a single file per level. Write your own answer first, run it yourself, and only then read the key. Everything in this article is illustrative; nothing here has been executed on your machine, so your own test run is the real check.
Free 2-Week CareerSwami Internship
Online, 14 days, with a free offer letter and a free completion certificate once you finish the work. Check with your college that it fits your requirement before you enrol.
Graded scorecard: what each level trains
| Level | Exercise focus | Pydantic features | Target time |
|---|---|---|---|
| 1 | Typed models and required fields | BaseModel, type hints, Optional, defaults |
30 min |
| 2 | Constraints on values | Field with ge, le, min_length, pattern; Literal |
45 min |
| 3 | Custom rules | field_validator, model_validator |
60 min |
| 4 | Bad requests in an endpoint | FastAPI, 422 handling, HTTPException |
60 min |
| 5 | Useful error responses | Custom exception handler, ValidationError.errors() |
75 min |
| 6 | Nested data and lists | Nested models, list[Model], strict mode |
75 min |
Score yourself per exercise: 2 points if it works and rejects the bad cases, 1 point if it works but misses an edge case, 0 if it fails. A full set is worth 24 points across the 12 exercises.
Level 1 and 2 exercises: models, types and constraints
Level 1: typed models
- Exercise 1.1. Create a
Studentmodel withname: str,age: intandemail: str. Instantiate it with a valid dictionary, then withage="abc". Print the exception. - Exercise 1.2. Add an optional
phonefield that defaults toNone, and ais_activefield that defaults toTrue. Show that omitting both still works but omittingnamefails.
Level 2: constraints
- Exercise 2.1. Constrain
ageto 16 to 100,nameto 2 to 60 characters, androll_noto the pattern of two letters followed by six digits. - Exercise 2.2. Add a
coursefield that accepts only"BCA","BCom"or"BSc"usingLiteral. Test with"MBA". - Exercise 2.3. Create a
Productmodel wherepricemust be greater than 0 andquantitymust be a non-negative integer. Test withprice=0andquantity=-1.
Level 3 to 6 exercises: custom rules, endpoints and error design
Level 3: custom validators
- Exercise 3.1. Write a
field_validatorthat strips whitespace fromname, rejects an empty result, and title-cases it. - Exercise 3.2. Create a
SignUpmodel withpasswordandconfirm_password. Use amodel_validatorto reject mismatches and passwords shorter than 8 characters without a digit.
Level 4: bad requests in an endpoint
- Exercise 4.1. Build
POST /studentsthat accepts yourStudentmodel. Send a body with a missing field using curl or the FastAPI/docspage and read the 422 response. - Exercise 4.2. Add a business rule outside Pydantic: reject a duplicate
roll_nowithHTTPException(status_code=409). Explain in a comment why this is 409 and not 422.
Level 5: useful errors
- Exercise 5.1. Register a handler for
RequestValidationErrorthat returns JSON shaped like{"error": "validation_failed", "fields": [{"field": "age", "message": "..."}]}.
Level 6: nested data
- Exercise 6.1. Model an
Ordercontaining acustomer(nested model) and a list ofitemswith at least one entry. Send an item with a negative quantity and find the exact error location. - Exercise 6.2. Turn on strict mode for
quantityso that the string"3"is rejected instead of silently converted. Decide when each behaviour is appropriate.
Answer key with the reasoning behind each validation choice
Treat these as illustrative reference solutions. Variations are fine as long as they reject the same bad inputs.
Answer key, Levels 1 and 2
from typing import Optional, Literal
from pydantic import BaseModel, Field
class Student(BaseModel):
name: str = Field(min_length=2, max_length=60)
age: int = Field(ge=16, le=100)
email: str
roll_no: str = Field(pattern=r"^[A-Za-z]{2}\d{6}$")
course: Literal["BCA", "BCom", "BSc"]
phone: Optional[str] = None
is_active: bool = True
- 1.1:
age="abc"raises aValidationErrorwith typeint_parsing. Pydantic does not crash with aValueErrorfrom your own code; it collects every failing field in one error object, which is why clients can fix all problems in a single round trip. - 1.2: A field with a default is optional; a field without one is required.
Optional[str] = Nonesays the value may be null and may be absent. Those are two separate ideas, and mixing them up is a frequent source of bugs. - 2.1:
ge/leare inclusive bounds;gt/ltare exclusive. Use inclusive bounds for ages and exclusive for “must be positive”. The pattern uses anchors (^and$) so that extra characters cannot hide around a valid core. - 2.2:
Literalgives you an allow-list. An allow-list is safer than checking for bad values, because you cannot anticipate every bad value. - 2.3: Use
Field(gt=0)for price andField(ge=0)for quantity. For money in real systems, preferDecimaloverfloatto avoid rounding surprises.
Answer key, Levels 3 and 4
from pydantic import BaseModel, field_validator, model_validator
class SignUp(BaseModel):
name: str
password: str
confirm_password: str
@field_validator("name")
@classmethod
def clean_name(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("name must not be blank")
return v.title()
@model_validator(mode="after")
def check_passwords(self):
if self.password != self.confirm_password:
raise ValueError("passwords do not match")
if len(self.password) < 8 or not any(c.isdigit() for c in self.password):
raise ValueError("password needs 8+ characters and a digit")
return self
- 3.1: A field validator sees one value, so it fits cleaning and single-field rules. It must return the value, otherwise the field becomes
None. RaiseValueError(not a custom crash) so Pydantic wraps it in its error report. - 3.2: A model validator runs after all fields are parsed, so it can compare two fields. Cross-field rules must live here, because a field validator cannot reliably see sibling values.
- 4.1: FastAPI returns 422 Unprocessable Entity with a
detaillist holdingloc,msgandtype. Thelocpath such as["body", "age"]tells you where the fault is. - 4.2: The body is well-formed but conflicts with existing state, so 409 Conflict fits. 422 means the payload itself is invalid. Pydantic cannot know about your database, so uniqueness checks belong in your service code.
Answer key, Levels 5 and 6
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def handle_validation(request: Request, exc: RequestValidationError):
fields = [
{"field": ".".join(str(p) for p in e["loc"][1:]),
"message": e["msg"]}
for e in exc.errors()
]
return JSONResponse(status_code=422,
content={"error": "validation_failed", "fields": fields})
- 5.1: Dropping the first
locelement removes the word “body”, which is noise to a client. Keep one stable error code (validation_failed) so front-end code can branch on it, and keep the human message separate. Avoid echoing raw input values for password fields. - 6.1: For a bad item the location looks like
["body", "items", 0, "quantity"]. The integer is the list index, which lets a client highlight the exact row. AddField(min_length=1)toitemsto refuse empty orders. - 6.2: Strict mode (
Field(strict=True)orConfigDict(strict=True)) refuses type coercion. Lax mode is friendlier for form data and query strings, where everything arrives as text. Strict mode suits internal service-to-service JSON where a string where a number belongs signals a real bug.
Worked illustrative example: grading one bad request end to end
Suppose a client sends this body to POST /students (illustrative input):
{"name": " ", "age": 14, "email": "ravi@example.com",
"roll_no": "ab12", "course": "MBA"}
With the Level 2 model, you would expect four reported problems in a single response: the name is too short, the age is below 16, the roll number does not match the pattern and the course is not in the allowed list. Under the Level 5 handler, the response would resemble:
{"error": "validation_failed",
"fields": [
{"field": "name", "message": "String should have at least 2 characters"},
{"field": "age", "message": "Input should be greater than or equal to 16"},
{"field": "roll_no", "message": "String should match pattern ..."},
{"field": "course", "message": "Input should be 'BCA', 'BCom' or 'BSc'"}
]}
Exact message wording varies by Pydantic version, so run it yourself and compare. Score yourself: if all four fields appear, you earn full marks for this scenario. If only the first error appears, you are probably raising exceptions manually instead of letting Pydantic collect them.
Mapping the exercises onto a 14-day internship plan
The free two-week online internship from CareerSwami runs 14 days, with a free offer letter and a free completion certificate after successful completion. If you pick a backend track, this exercise set fits that window well.
| Days | Activity | Output to keep |
|---|---|---|
| 1-2 | Setup, Level 1 and 2 exercises | Working Student and Product models |
| 3-4 | Level 3 custom validators; write 10 pass/fail test inputs | A test file with expected outcomes |
| 5-6 | Level 4 endpoints, status code choices | Running API with 422 and 409 cases |
| 7-8 | Level 5 error handler and response contract | Written error format document |
| 9-10 | Level 6 nested orders and strict mode | Order endpoint with nested errors |
| 11-12 | Combine into a small mini-project (student registry API) | Git repository with README |
| 13 | Add pytest tests for every rejection rule | Passing test suite |
| 14 | Clean up, document, self-score against the table | Final write-up |
CSJMU students: where Pydantic practice fits your course context
CSJMU in Kanpur, formerly Kanpur University, describes itself on its Academic Calendar page as holding an A++ NAAC rating. The same page lists separate calendars for the University Campus and for Affiliated Colleges, including a 2026-27 Odd Semester calendar for the campus. Since your timetable depends on which calendar applies to you, check it before scheduling a 14-day block.
For computing students, the university hosts a proposed BCA syllabus PDF with semester-coded papers such as BCA-S101T Computer Fundamental & Office Automation. That is an excerpt-level view and may be outdated, so read the current version for your batch. Python API work is self-directed practice that complements whatever your syllabus covers; do not assume it maps to a specific paper.
On internships, the B.Com. (Honors) ordinance (a 2019 document by file name) mentions six to eight weeks of summer internship in a business or industrial unit. It gives no credits, evaluation scheme or mention of online internships. Rules for other programmes were not verified, so a two-week online internship should be treated as extra learning unless your department confirms otherwise. It does not carry stated academic credit.
The same ordinance says programmes should have at least one computer lab and campus Wi-Fi, but do not rely on that for practice time; a personal laptop setup is more dependable.
Verification steps for CSJMU students before you count this work
- Identify whether you study on the University Campus or at an affiliated college, then open the matching calendar on the Academic Calendar page.
- Download the latest syllabus PDF for your exact programme and batch, and check whether it names a project, training or internship component.
- Email your department coordinator asking in writing: “Is an online internship accepted for the training or project requirement, and what documents are required?”
- Ask whether the rules are set by the university or by your college, and request the circular or ordinance number.
- If you want to cite the internship in a project file, ask your faculty guide whether a completion certificate is accepted as supporting evidence.
Self-review checklist for validation quality
- Every required field is truly required, and every optional field has a deliberate default.
- Numbers have both lower and upper bounds where sensible.
- Strings have maximum lengths, so one request cannot send megabytes into a name field.
- Enumerated values use
LiteralorEnum. - Cross-field rules sit in a model validator.
- Business-state conflicts return 409, not 422.
- Error responses use one consistent shape with stable machine-readable codes.
- Error messages never reveal secrets, stack traces or passwords.
- You have at least one test per rejection rule.
Status codes for bad requests: which one to return
| Situation | Status | Reason |
|---|---|---|
| Body is not valid JSON | 400 | The request cannot be parsed at all |
| JSON is valid but fields fail rules | 422 | Syntax fine, content invalid (FastAPI default) |
| Duplicate unique value | 409 | Conflicts with current server state |
| Missing or bad credentials | 401 | Caller is not authenticated |
| Authenticated but not allowed | 403 | Permission problem, not a data problem |
| Record ID does not exist | 404 | Resource not found |
Frequently Asked Questions
Should I validate in Pydantic or in my route function?
Put shape, type, range and format rules in the Pydantic model, and put rules that need a database or another service in route or service code. That split keeps models reusable and easy to test without any infrastructure.
Why does Pydantic accept the string “25” for an integer field?
By default Pydantic runs in lax mode and converts safely convertible values. If you want a string rejected, use strict mode on that field or on the whole model.
What is the difference between a 400 and a 422 response?
400 usually signals a malformed request that could not be parsed, while 422 signals a parsed request whose values break your rules. FastAPI uses 422 for model validation failures.
Can I return all errors at once instead of the first one?
Yes. Pydantic collects every failing field into a single ValidationError, and errors() returns them as a list. Raising your own exception inside route code stops at the first problem, so keep field rules inside the model.
How do I validate an email properly?
Install the optional extra with pip install "pydantic[email]" and use EmailStr. A regular expression alone is a weak check, and the only true confirmation is sending a verification email.
Is Pydantic validation enough to stop injection attacks?
No. It limits what shapes of data get in, but you still need parameterised database queries and output escaping. Treat validation as one layer, not the whole defence.
Will completing these exercises count for credit at CSJMU?
Nothing verified states that it will, and the internship is not credit-bearing. Ask your department in writing whether any certificate is accepted for project or training requirements.
Do I need FastAPI to use Pydantic?
No. Pydantic works in any Python code, including scripts, Flask apps and data pipelines. FastAPI simply connects it to HTTP requests and responses automatically.
Related Python & Backend Engineering guides
- Birla Global University Students: Business-Minded Backend Internships in Order and Billing Systems
- Assam Women's University Students: Building a Backend Internship Study Group That Works
- Noble University Students: Background Tasks and Queues Explained for Backend Interns
- All Python & Backend Engineering internship guides
Ready to put this into practice?
Join the free two-week CareerSwami online internship in Python & Backend Engineering. The internship, the offer letter and the completion certificate are free.










