Mathew K Analytics

Lesson 6 · FastAPI deep dive

FastAPI Tutorial #6: Path Operation Configuration & Better Docs

Video six of the eighteen-part series: shaping how endpoints appear in the auto-generated docs. Tags, summaries, descriptions, deprecation, and documenting…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

FastAPI Deep-Dive, Video 6: Path Operation Configuration#

  • Video six of the eighteen-part series: shaping how endpoints appear in the auto-generated docs.
  • Tags, summaries, descriptions, deprecation, and documenting multiple response codes.
  • Let's get into it.

Part 1: tags - Grouping Endpoints in the Docs#

from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get('/users', tags=['users'])
def list_users():
    return [{'id': 1, 'name': 'Alex'}]
@app.get('/orders', tags=['orders'])
def list_orders():
    return [{'id': 100, 'total': 42.5}]
client = TestClient(app)
schema = app.openapi()
print(schema['paths']['/users']['get']['tags'])
print(schema['paths']['/orders']['get']['tags'])
['users']
['orders']

Part 2: summary and description#

@app.get(
    '/products',
    summary='List all products',
    description='Returns every product currently in the catalog, unfiltered.',
)
def list_products():
    return [{'id': 1, 'name': 'Mug'}]
app.openapi_schema = None
schema = app.openapi()
print(schema['paths']['/products']['get']['summary'])
print(schema['paths']['/products']['get']['description'])
List all products
Returns every product currently in the catalog, unfiltered.

Part 3: Docstrings Become the Description Automatically#

@app.get('/categories')
def list_categories():
    """Return the full list of product categories, sorted alphabetically."""
    return ['electronics', 'kitchen', 'outdoor']
app.openapi_schema = None
schema = app.openapi()
print(schema['paths']['/categories']['get']['description'])
Return the full list of product categories, sorted alphabetically.

Part 4: response_description#

@app.post(
    '/products',
    status_code=201,
    response_description='The newly created product',
)
def create_product():
    return {'id': 2, 'name': 'Plate'}
app.openapi_schema = None
schema = app.openapi()
print(schema['paths']['/products']['post']['responses']['201']['description'])
The newly created product

Part 5: deprecated=True#

@app.get('/legacy-users', deprecated=True)
def legacy_list_users():
    return [{'id': 1, 'name': 'Alex'}]
app.openapi_schema = None
schema = app.openapi()
print(schema['paths']['/legacy-users']['get']['deprecated'])
print(client.get('/legacy-users').status_code)
True
200

Part 6: responses - Documenting Multiple Status Codes#

from fastapi import HTTPException
@app.get(
    '/products/{product_id}',
    responses={404: {'description': 'Product not found'}},
)
def get_product(product_id: int):
    if product_id != 1:
        raise HTTPException(status_code=404, detail='not found')
    return {'id': 1, 'name': 'Mug'}
app.openapi_schema = None
schema = app.openapi()
print(schema['paths']['/products/{product_id}']['get']['responses']['404'])
print(client.get('/products/1').status_code)
print(client.get('/products/99').status_code)
{'description': 'Product not found'}
200
404

Part 7: include_in_schema=False - Hiding Internal Endpoints#

@app.get('/internal/debug', include_in_schema=False)
def debug_info():
    return {'status': 'ok', 'internal': True}
app.openapi_schema = None
schema = app.openapi()
print('/internal/debug' in schema['paths'])
print(client.get('/internal/debug').json())
False
{'status': 'ok', 'internal': True}

Part 8: operation_id - Explicit Client Generation Names#

@app.get('/reports/summary', operation_id='get_summary_report')
def summary_report():
    return {'total_orders': 12, 'total_revenue': 458.75}
app.openapi_schema = None
schema = app.openapi()
print(schema['paths']['/reports/summary']['get']['operationId'])
get_summary_report

Part 9: Combining Several Configuration Options Together#

@app.delete(
    '/products/{product_id}',
    tags=['products'],
    summary='Delete a product',
    description='Permanently removes a product from the catalog by id.',
    response_description='Confirmation that the product was deleted',
    responses={404: {'description': 'Product not found'}},
)
def delete_product(product_id: int):
    if product_id != 1:
        raise HTTPException(status_code=404, detail='not found')
    return {'deleted': product_id}
app.openapi_schema = None
schema = app.openapi()
entry = schema['paths']['/products/{product_id}']['delete']
print(entry['tags'], entry['summary'])
print(client.delete('/products/1').json())
['products'] Delete a product
{'deleted': 1}

Part 10: A Real Pattern - a Fully Documented Endpoint Group#

inventory_app = FastAPI()
@inventory_app.get(
    '/inventory/{sku}',
    tags=['inventory'],
    summary='Get stock level for a SKU',
    description='Looks up the current stock quantity for a given product SKU.',
    responses={404: {'description': 'SKU not found'}},
)
def get_stock(sku: str):
    catalog = {'MUG-1': 40, 'PLATE-2': 15}
    if sku not in catalog:
        raise HTTPException(status_code=404, detail='unknown sku')
    return {'sku': sku, 'quantity': catalog[sku]}
@inventory_app.post(
    '/inventory/{sku}/restock',
    tags=['inventory'],
    summary='Restock a SKU',
    description='Adds units to the current stock quantity for a given product SKU.',
    responses={404: {'description': 'SKU not found'}},
)
def restock(sku: str, amount: int):
    catalog = {'MUG-1': 40, 'PLATE-2': 15}
    if sku not in catalog:
        raise HTTPException(status_code=404, detail='unknown sku')
    return {'sku': sku, 'quantity': catalog[sku] + amount}
inventory_client = TestClient(inventory_app)
print(inventory_client.get('/inventory/MUG-1').json())
print(inventory_client.post('/inventory/MUG-1/restock', params={'amount': 10}).json())
print(inventory_client.get('/inventory/UNKNOWN').status_code)
{'sku': 'MUG-1', 'quantity': 40}
{'sku': 'MUG-1', 'quantity': 50}
404

Wrap-Up: What You Learned#

  • tags group related endpoints together under the same heading in the docs.
  • summary and description supply a short title and longer explanation for an endpoint.
  • A handler's own docstring becomes the description automatically when none is given.
  • response_description labels what the success response itself represents.
  • deprecated=True marks an endpoint as outdated in the docs without breaking it.
  • responses documents additional status codes, like 404, with their own description.
  • include_in_schema=False hides an endpoint from the docs while it keeps working.
  • operation_id gives an endpoint a stable, explicit name for client SDK generation.
  • All of these options stack together freely on a single endpoint.
  • That wraps up path operation configuration. Next up: Dependency Injection Basics.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.