FastAPI Backend Development Bootcamp · Lekcja

Dynamiczne parametry filtrowania i sortowania

Buduj wielokrotnego użytku modele parametrów zapytań do filtrowania, sortowania i wyboru pól wraz z walidacją.

Lekcja 3 z 413 kroki

Dynamiczne parametry filtrowania i sortowania to bezpłatna lekcja FastAPI Backend Development Bootcamp na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej FastAPI Backend Development Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs FastAPI Backend Development Bootcamp zawiera 4 lekcji w sumie.

Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.

Why Dynamic Query Parameters?

Real-world list endpoints rarely return everything. Clients want to filter (only active users), sort (newest first), and select fields (just id and name). Hardcoding every combination explodes your route count.

The clean approach is to model these query parameters as reusable, validated objects that you inject into many endpoints. In this lesson we build:

  • A filter model that turns query params into safe constraints
  • A sort parser with an allow-list of fields and directions
  • A field selection mechanism to trim response payloads

Everything is driven by FastAPI dependencies so it stays DRY and testable.

Collecting Filters with a Dependency Class

A class with __init__ taking Query parameters becomes a reusable dependency. FastAPI reads each parameter from the URL and documents it in OpenAPI automatically.

Use Optional[...] = None so filters are opt-in: a missing param means "don't filter on this column".

from typing import Optional
from fastapi import Query

class UserFilterParams:
    def __init__(
        self,
        status: Optional[str] = Query(None, description="active | inactive"),
        min_age: Optional[int] = Query(None, ge=0, le=150),
        search: Optional[str] = Query(None, min_length=2, max_length=50),
    ):
        self.status = status
        self.min_age = min_age
        self.search = search

# Usage:
# @app.get('/users')
# def list_users(filters: UserFilterParams = Depends()):
#     ...

Validating Filter Values with Enums

Free-text filters like status=foo let bad input through. Constrain them with a str-based Enum: FastAPI rejects anything outside the allowed set and renders a dropdown in the docs.

This is the first line of defence — invalid filter values get a clean 422 instead of leaking into your query layer.

from enum import Enum
from typing import Optional
from fastapi import Query

class UserStatus(str, Enum):
    active = "active"
    inactive = "inactive"
    pending = "pending"

class UserFilterParams:
    def __init__(
        self,
        status: Optional[UserStatus] = Query(None),
        min_age: Optional[int] = Query(None, ge=0, le=150),
    ):
        self.status = status
        self.min_age = min_age

Turning Filters into Predicates

Keep the HTTP layer separate from the data layer. The dependency only collects and validates; a small helper converts the populated object into actual filter predicates.

Here is a framework-free version you can run, applying filters over plain dicts. The same pattern maps cleanly onto SQLAlchemy .filter() calls later.

USERS = [
    {"id": 1, "name": "Ada", "status": "active", "age": 36},
    {"id": 2, "name": "Linus", "status": "inactive", "age": 54},
    {"id": 3, "name": "Grace", "status": "active", "age": 41},
]

def apply_filters(rows, status=None, min_age=None, search=None):
    result = rows
    if status is not None:
        result = [r for r in result if r["status"] == status]
    if min_age is not None:
        result = [r for r in result if r["age"] >= min_age]
    if search is not None:
        result = [r for r in result if search.lower() in r["name"].lower()]
    return result

print(apply_filters(USERS, status="active", min_age=40))

Parsing a Sort Parameter

A common contract is ?sort=-created_at,name: a comma-separated list where a leading - means descending. Parse it into (field, direction) tuples.

Never trust the client's field names. Validate each field against an allow-list so users can't sort by, or probe, arbitrary columns.

ALLOWED_SORT = {"created_at", "name", "age", "id"}

def parse_sort(sort_param):
    parsed = []
    for token in sort_param.split(","):
        token = token.strip()
        if not token:
            continue
        descending = token.startswith("-")
        field = token[1:] if descending else token
        if field not in ALLOWED_SORT:
            raise ValueError(f"Cannot sort by '{field}'")
        parsed.append((field, "desc" if descending else "asc"))
    return parsed

print(parse_sort("-created_at,name"))
print(parse_sort("age"))

A Reusable Sort Dependency

Wrap the parser in a dependency so every list endpoint shares the same sort contract and validation. Raising HTTPException(422) on a bad field gives clients a precise, machine-readable error.

Passing the allow-list in makes the dependency reusable across resources with different sortable columns.

from typing import Optional
from fastapi import Query, HTTPException

def sort_dependency(allowed: set):
    def _parse(sort: Optional[str] = Query(None, example="-created_at,name")):
        if not sort:
            return []
        parsed = []
        for token in sort.split(","):
            token = token.strip()
            if not token:
                continue
            desc = token.startswith("-")
            field = token[1:] if desc else token
            if field not in allowed:
                raise HTTPException(422, f"Invalid sort field: {field}")
            parsed.append((field, "desc" if desc else "asc"))
        return parsed
    return _parse

# @app.get('/users')
# def list_users(sort=Depends(sort_dependency({'created_at','name'}))):
#     ...

Applying Multi-Key Sort In Memory

Multiple sort keys must be applied in order. A stable trick: sort by the least significant key first and work backwards, because Python's sorted is stable.

This standalone example mirrors what a database ORDER BY a, b DESC would produce.

ROWS = [
    {"name": "Ada", "age": 36},
    {"name": "Grace", "age": 36},
    {"name": "Linus", "age": 54},
]

def apply_sort(rows, sort_keys):
    result = list(rows)
    for field, direction in reversed(sort_keys):
        result.sort(key=lambda r: r[field], reverse=(direction == "desc"))
    return result

ordered = apply_sort(ROWS, [("age", "desc"), ("name", "asc")])
for r in ordered:
    print(r)

Field Selection (Sparse Fieldsets)

To shrink payloads, support ?fields=id,name. The client picks which keys come back. As always, validate against an allow-list of exposable fields so internal columns (like password_hash) can never be requested.

Selection is a projection step you apply after filtering and sorting, just before serialization.

EXPOSABLE = {"id", "name", "status", "age"}

def select_fields(rows, fields_param):
    if not fields_param:
        return rows
    requested = {f.strip() for f in fields_param.split(",") if f.strip()}
    invalid = requested - EXPOSABLE
    if invalid:
        raise ValueError(f"Unknown fields: {sorted(invalid)}")
    return [{k: r[k] for k in requested if k in r} for r in rows]

data = [{"id": 1, "name": "Ada", "status": "active", "age": 36}]
print(select_fields(data, "id,name"))

Combining Filter, Sort, Select and Pagination

The pipeline order matters for correctness and efficiency: filter first to reduce the set, then sort, then paginate (slice), and finally select fields on the page you return.

Selecting fields before pagination would still scan everything, and paginating before sorting would return the wrong page.

def list_resource(rows, *, filters, sort_keys, fields, offset, limit,
                  apply_filters, apply_sort, select_fields):
    rows = apply_filters(rows, **filters)
    rows = apply_sort(rows, sort_keys)
    total = len(rows)
    page = rows[offset: offset + limit]
    page = select_fields(page, fields)
    return {"total": total, "items": page,
            "offset": offset, "limit": limit}

# In FastAPI each piece is a Depends(); the route just calls list_resource.

Composing Dependencies into One Query Object

Rather than passing four separate dependencies into every route, compose them. A wrapper dependency can return one tidy object holding filters, sort keys, fields, and pagination.

This keeps route signatures short and gives you a single place to evolve the query contract.

from dataclasses import dataclass
from typing import Optional
from fastapi import Depends, Query

@dataclass
class ListQuery:
    filters: object
    sort: list
    fields: Optional[str]
    offset: int
    limit: int

def list_query(
    filters: "UserFilterParams" = Depends(),
    sort: list = Depends(sort_dependency({"created_at", "name"})),
    fields: Optional[str] = Query(None),
    offset: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
) -> ListQuery:
    return ListQuery(filters, sort, fields, offset, limit)

# @app.get('/users')
# def list_users(q: ListQuery = Depends(list_query)):
#     ...

Documenting and Defaulting the Contract

A good query contract is self-documenting and safe by default:

  • Give every Query a description and an example so the OpenAPI docs explain the syntax.
  • Cap limit with le=100 so a client can't request a million rows.
  • Choose a sensible default sort (e.g. newest first) so results are deterministic across pages.
  • Reject unknown fields/sort keys with 422 instead of silently ignoring them.

Deterministic ordering is critical: without a stable sort, pagination can repeat or skip rows between requests.

Quick Check: Pipeline Order

You expose GET /products supporting filtering, sorting, pagination, and sparse fieldsets. In what order should these operations be applied to return the correct page efficiently?

Recap

You built a reusable, validated query layer for FastAPI list endpoints:

  • Filters as a dependency class with Optional params and Enum/constraint validation.
  • Sorting parsed from -field,field syntax against an allow-list, raising 422 on unknown fields.
  • Field selection (sparse fieldsets) restricted to an exposable allow-list to protect internal columns.
  • A composed ListQuery dependency that keeps route signatures clean.

Remember the pipeline: filter → sort → paginate → select, always with a deterministic default sort so pagination stays consistent. These patterns map directly onto SQLAlchemy queries when you move from in-memory data to a real database.

Bezpłatny start

Ucz się FastAPI Backend Development Bootcamp dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
21
Lekcje
84

Często zadawane pytania

Czy lekcja „Dynamiczne parametry filtrowania i sortowania” jest bezpłatna?

Tak — pełny tekst „Dynamiczne parametry filtrowania i sortowania” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu FastAPI Backend Development Bootcamp, przejdź na CoddyKit PRO. Kurs FastAPI Backend Development Bootcamp zawiera 4 lekcji w sumie.

Co nauczysz się w „Dynamiczne parametry filtrowania i sortowania”?

Buduj wielokrotnego użytku modele parametrów zapytań do filtrowania, sortowania i wyboru pól wraz z walidacją. Ćwiczysz FastAPI Backend Development Bootcamp z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć FastAPI Backend Development Bootcamp?

Nie wymagamy żadnego doświadczenia. FastAPI Backend Development Bootcamp w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Dynamiczne parametry filtrowania i sortowania”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji FastAPI Backend Development Bootcamp?

Tak. Każda lekcja FastAPI Backend Development Bootcamp zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Wersjonowanie za pomocą adresu URL, nagłówka i typu MIME
  2. Paginacja kursorowa a offsetowa w dużej skali
  3. Dynamiczne parametry filtrowania i sortowania
  4. Projektowanie stabilnych obwiedzi odpowiedzi
← Powrót do FastAPI Backend Development Bootcamp