Python course Β· Module 12: Final Project
Technical Documentation
In this lesson3
The best savanna guide is useless if their knowledge disappears with them. A new expedition member then gets only a copy of the tracks without a legend. Code works the same way: a project without documentation can be started by one person, and after three months even that person forgets how they did it. Documentation is the bridge between code and people. Good documentation keeps the project alive even while you sleep, and lets a recruiter or a new teammate understand it in five minutes.
We build documentation on three levels: a README for someone who sees the repository for the first time, docstrings for the person reading the code, and generated API documentation for those who use it. A good order of work is the README first, then docstrings, then configuring the generated API docs, and finally CHANGELOG.md, the log of changes across versions.
README.md - The Project's Business Card
The README is the first thing GitHub shows below the file list. A typical order of sections: title, description or features, quick start, and the license at the end. Here is a complete example for our assistant:
1# AI Document Assistant
2
3An intelligent assistant for searching documents using RAG.
4
5## Features
6
7- Semantic search across documents
8- Answers in natural language
9- Support for PDF, DOCX, TXT
10- Fast responses (<2s)
11
12## Quick Start
13
14### Requirements
15- Python 3.12+
16- Docker (for Qdrant)
17
18### Installation
19
20```bash
21# Clone the repo
22git clone https://github.com/user/ai-doc-assistant.git
23cd ai-doc-assistant
24
25# Create environment
26python -m venv venv
27source venv/bin/activate
28
29# Install dependencies
30pip install -r requirements.txt
31
32# Launch Qdrant
33docker compose up -d qdrant
34
35# Set environment variables
36cp .env.example .env
37# Edit .env and add OPENAI_API_KEY
38
39# Run the application
40uvicorn app.main:app --reload
41```
42
43## API Documentation
44
45After launching: http://localhost:8000/docs
46
47## Architecture
48
49[Link to architecture diagram]
50
51## Testing
52
53```bash
54pytest tests/ -v
55```
56
57## License
58
59MITThe installation guide is a sequence of commands you can copy and run: clone, virtual environment, dependencies, database, environment variables, start. Pay attention to cp .env.example .env: real keys never go into the repository, only a template of the file. docker compose (with a space) is the current version of the tool, built into Docker as a plugin.
Docstrings - In-Code Documentation
A docstring is a string in triple quotes right below a class or function definition. PEP 257 describes the general conventions, and you choose the format of the sections. The most popular are Google style and NumPy style, both understood by Sphinx (the napoleon extension). We use Google style: the Attributes, Args, Returns, Raises and Example sections.
1from typing import Optional
2
3class RAGService:
4 """
5 RAG service for answering questions based on documents.
6
7 This service implements the full RAG pipeline:
8 1. Embedding the question
9 2. Searching for similar documents
10 3. Generating a response with context
11
12 Attributes:
13 vector_store: Vector database client
14 llm: Language model client
15 embedder: Embedding service
16
17 Example:
18 >>> service = RAGService(vector_store, llm, embedder)
19 >>> response = await service.query("What is Python?")
20 >>> print(response.answer)
21 Python is a programming language...
22 """The class docstring says what the service does and what it consists of. The example in >>> format shows usage, and the line below it is the printed result. The constructor is simple and needs no separate description:
1 def __init__(
2 self,
3 vector_store: VectorStore,
4 llm: LLMClient,
5 embedder: EmbeddingService
6 ):
7 self.vector_store = vector_store
8 self.llm = llm
9 self.embedder = embedderThe argument types (VectorStore, LLMClient, EmbeddingService) are interfaces from the infrastructure layer, following the architecture from the previous lesson. The public method needs the most description:
1 async def query(
2 self,
3 question: str,
4 top_k: int = 5,
5 filters: Optional[dict] = None
6 ) -> Response:
7 """
8 Performs a RAG query.
9
10 Args:
11 question: User question in natural language
12 top_k: Number of documents to retrieve (default: 5)
13 filters: Optional metadata filters
14
15 Returns:
16 Response: Object containing the answer and sources
17
18 Raises:
19 ValueError: When the question is empty
20 LLMError: When a generation error occurs
21
22 Example:
23 >>> response = await service.query(
24 ... "How does RAG work?",
25 ... top_k=3,
26 ... filters={"category": "ai"}
27 ... )
28 """
29 if not question.strip():
30 raise ValueError("Question cannot be empty")
31
32 # Implementation...The Raises section is often skipped, yet it is the one that tells you what can go wrong. You can read a docstring in the console with help(RAGService.query), so it is documentation that is always at hand. Note: the example with await is meant for reading, a plain doctest will not run it.
API Documentation with FastAPI
FastAPI generates API documentation in the OpenAPI standard (formerly Swagger) automatically, based on types and Pydantic models. First, the application metadata:
1from fastapi import FastAPI, HTTPException
2from pydantic import BaseModel, Field
3
4app = FastAPI(
5 title="AI Document Assistant API",
6 description="REST API for an intelligent document assistant",
7 version="1.0.0",
8 docs_url="/docs",
9 redoc_url="/redoc"
10)Under /docs you get the interactive Swagger UI, under /redoc the alternative ReDoc view. Now the request and response models. Pydantic validates the data, and Field adds constraints and descriptions:
1class QueryRequest(BaseModel):
2 """RAG query request."""
3 question: str = Field(
4 ...,
5 description="Question in natural language",
6 examples=["What is machine learning?"]
7 )
8 top_k: int = Field(
9 default=5,
10 ge=1,
11 le=20,
12 description="Number of source documents"
13 )
14
15class QueryResponse(BaseModel):
16 """Response from the RAG system."""
17 answer: str = Field(description="Generated answer")
18 sources: list[dict] = Field(description="Source documents")
19 latency_ms: float = Field(description="Response time in ms")ge=1, le=20 means "from 1 to 20", so top_k=50 is rejected with a 422 error before it reaches your code. Example values are given with examples=[...]. The old example= parameter is deprecated in Pydantic v2 and produces a warning. Finally, the endpoint:
1@app.post(
2 "/query",
3 response_model=QueryResponse,
4 summary="Ask a question",
5 description="Performs a RAG query and returns an answer with sources"
6)
7async def query(request: QueryRequest) -> QueryResponse:
8 """
9 Endpoint for asking questions to the RAG system.
10
11 - **question**: Question in natural language
12 - **top_k**: Number of documents to search
13 """
14 passsummary and description appear in Swagger UI, and so does the docstring with its markdown. You do not write any separate documentation file, the schema is built from the code, so it cannot drift away from reality.
My advice: write the README on the same day you create the repository, not at the end of the project. Documentation written as you go is shorter and more truthful. In the next lesson we move on to deployment.
Remember: documentation is the expedition journal that lets the next expedition follow your tracks without guessing.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What format do Python docstrings use most often?
2. Which tool is most commonly used for REST API documentation?
Hands-on tasks in the game
- Code editor
Write a Python docstring for a class with a description of parameters and methods
- Vertical ordering
Arrange the elements of project documentation from most important to least important:
- Horizontal ordering
Arrange the basic README.md sections in the correct order: