feat(api): sort supporté sur colonnes groupby en mode agrégation (#78)

Testé contre tabular-api data.gouv.fr : sort sur une colonne groupby
est honoré (200), sort sur un alias d'agrégat retourne 400 (colonne
inconnue). aggregate_marches reçoit order_by ; doc Swagger mise à jour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-06-22 14:20:02 +02:00
parent b87eb4328c
commit 70ee819a51
2 changed files with 14 additions and 6 deletions
+8 -3
View File
@@ -141,9 +141,12 @@ def schema():
"la colonne de sortie est nommée `colonne__count`, `colonne__sum`, " "la colonne de sortie est nommée `colonne__count`, `colonne__sum`, "
"`colonne__avg`, `colonne__min`, `colonne__max`\n\n" "`colonne__avg`, `colonne__min`, `colonne__max`\n\n"
"En mode agrégation, la réponse contient des lignes groupées, " "En mode agrégation, la réponse contient des lignes groupées, "
"`columns` est interdit et `meta` ne contient pas `total`.\n\n" "`columns` est interdit et `meta` ne contient pas `total`. "
"`sort` peut être appliqué sur une colonne `groupby` (ex. `acheteur_departement_code__sort=asc`) ; "
"il n'est pas supporté sur les alias d'agrégats (ex. `uid__count__sort=desc` → 400).\n\n"
"Exemples : `acheteur_id__contains=VILLE`, `montant__greater=10000`, " "Exemples : `acheteur_id__contains=VILLE`, `montant__greater=10000`, "
"`acheteur_departement_code__groupby&montant__sum`." "`acheteur_departement_code__groupby&montant__sum`, "
"`acheteur_departement_code__groupby&uid__count&acheteur_departement_code__sort=asc`."
), ),
}, },
], ],
@@ -159,7 +162,8 @@ def data():
Agrégation (drapeaux sans valeur) : `<colonne>__groupby`, Agrégation (drapeaux sans valeur) : `<colonne>__groupby`,
`<colonne>__count|sum|avg|min|max`. Les colonnes agrégées sont nommées `<colonne>__count|sum|avg|min|max`. Les colonnes agrégées sont nommées
`<colonne>__<opérateur>`. `columns` est interdit avec une agrégation et `<colonne>__<opérateur>`. `columns` est interdit avec une agrégation et
`meta` ne contient alors pas `total`. `meta` ne contient alors pas `total`. `sort` est supporté sur les colonnes
`groupby` ; non supporté sur les alias d'agrégats (→ 400).
Paramètres réservés : page (défaut 1), page_size (défaut 50, max 1000), Paramètres réservés : page (défaut 1), page_size (défaut 50, max 1000),
columns (csv), count_results (true|false ; mettre false pour économiser columns (csv), count_results (true|false ; mettre false pour économiser
@@ -193,6 +197,7 @@ def data():
where_sql=where_sql, where_sql=where_sql,
params=params, params=params,
group_by=agg.group_by_sql, group_by=agg.group_by_sql,
order_by=order_sql or None,
limit=page_size, limit=page_size,
offset=(page - 1) * page_size, offset=(page - 1) * page_size,
) )
+6 -3
View File
@@ -198,18 +198,21 @@ def aggregate_marches(
where_sql: str = "TRUE", where_sql: str = "TRUE",
params: tuple | list = (), params: tuple | list = (),
group_by: str | None = None, group_by: str | None = None,
order_by: str | None = None,
limit: int | None = None, limit: int | None = None,
offset: int | None = None, offset: int | None = None,
) -> pl.DataFrame: ) -> pl.DataFrame:
"""SELECT agrégé paramétré contre la table decp. """SELECT agrégé paramétré contre la table decp.
`select_sql` et `group_by` sont des fragments SQL construits depuis des `select_sql`, `group_by` et `order_by` sont des fragments SQL construits
noms de colonnes validés (jamais de valeur utilisateur libre). Les depuis des noms de colonnes validés (jamais de valeur utilisateur libre).
valeurs de filtre passent par le binding `?` via `params`. Les valeurs de filtre passent par le binding `?` via `params`.
""" """
sql = f"SELECT {select_sql} FROM decp WHERE {where_sql}" sql = f"SELECT {select_sql} FROM decp WHERE {where_sql}"
if group_by: if group_by:
sql += f" GROUP BY {group_by}" sql += f" GROUP BY {group_by}"
if order_by:
sql += f" ORDER BY {order_by}"
if limit is not None: if limit is not None:
sql += f" LIMIT {int(limit)}" sql += f" LIMIT {int(limit)}"
if offset is not None: if offset is not None: