AccueilBlogTest technique API REST pour la data : concevoir et consommer des APIs
Guide recrutement data

Test technique API REST pour la data : concevoir et consommer des APIs

Les APIs REST sont partout dans les pipelines data : ingestion de sources externes, exposition de features ML, interfaces avec les équipes métier. Savoir les concevoir et les consommer rigoureusement est indispensable.

Data Builder·Juin 2025·6 min de lecture·Data Engineer · Data Scientist
Sommaire
  1. Principes REST
  2. Authentification sécurisée
  3. Pagination
  4. Versioning d API
  5. Consommer une API robustement
  6. Exposer des données via API
  7. Grille

1Principes REST fondamentaux

Question discriminante

Quels sont les principes REST ? Quelle est la différence entre GET, POST, PUT et PATCH ?

MéthodeActionIdempotentExemple
GETLireOuiGET /orders/123
POSTCréerNonPOST /orders
PUTRemplacer entièrementOuiPUT /orders/123 (toutes les propriétés)
PATCHModifier partiellementNonPATCH /orders/123 (status seulement)
DELETESupprimerOuiDELETE /orders/123

2Authentification : API Keys vs OAuth2

Question discriminante

Quand utilisez-vous une API Key et quand OAuth2 pour authentifier une API data ?

import requests # API Key : simple, pour les services M2M (machine à machine) headers = {'X-API-Key': 'sk-prod-abc123...'} response = requests.get('https://api.service.com/data', headers=headers) # Bearer Token (JWT) : pour les utilisateurs headers = {'Authorization': f'Bearer {token}'} # OAuth2 Client Credentials : pour les services M2M avec rotation import httpx def get_token(client_id: str, client_secret: str, token_url: str) -> str: response = httpx.post(token_url, data={ 'grant_type': 'client_credentials', 'client_id': client_id, 'client_secret': client_secret }) return response.json()['access_token'] # Stocker les credentials dans les variables d environnement # JAMAIS en dur dans le code

3Pagination : cursor vs offset

Question discriminante

Quelle est la différence entre la pagination par offset et par cursor ? Laquelle préférez-vous ?

import requests # Offset pagination : simple mais problèmes sur données changeantes def get_all_orders_offset(url: str) -> list: all_orders = [] page = 1 while True: resp = requests.get(f'{url}?page={page}&limit=100').json() all_orders.extend(resp['data']) if not resp.get('next_page'): break page += 1 return all_orders # Cursor pagination : stable, recommandée pour les grandes APIs def get_all_orders_cursor(url: str) -> list: all_orders = [] cursor = None while True: params = {'limit': 100} if cursor: params['cursor'] = cursor resp = requests.get(url, params=params).json() all_orders.extend(resp['data']) cursor = resp.get('next_cursor') if not cursor: break return all_orders

4Versioning d API

Question discriminante

Comment versionnez-vous une API data pour ne pas casser les consommateurs existants ?

5Consommer une API robustement

Question discriminante

Comment gérez-vous les erreurs et le rate limiting quand vous consommez une API tierce ?

import requests import time from tenacity import retry, stop_after_attempt, wait_exponential @retry( stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=60) ) def fetch_with_retry(url: str, headers: dict) -> dict: response = requests.get(url, headers=headers, timeout=30) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) time.sleep(retry_after) raise Exception('Rate limited') response.raise_for_status() # raise pour 4xx et 5xx return response.json() # Respecter les rate limits proactivement from ratelimit import limits, sleep_and_retry @sleep_and_retry @limits(calls=100, period=60) # 100 appels par minute def call_api(url: str) -> dict: return requests.get(url).json()

6Exposer des données via API FastAPI

Question discriminante

Comment exposez-vous une table BigQuery ou Snowflake via une API REST ?

from fastapi import FastAPI, Query, Depends from pydantic import BaseModel from typing import Optional import duckdb app = FastAPI() class OrdersResponse(BaseModel): data: list total: int cursor: Optional[str] = None @app.get('/api/v1/orders', response_model=OrdersResponse) async def get_orders( region: Optional[str] = Query(None), date_from: Optional[str] = Query(None), limit: int = Query(100, ge=1, le=1000), cursor: Optional[str] = Query(None) ): query = 'SELECT * FROM orders WHERE 1=1' if region: query += f" AND region = '{region}'" if date_from: query += f" AND order_date >= '{date_from}'" if cursor: query += f" AND order_id > '{cursor}'" query += f' ORDER BY order_id LIMIT {limit + 1}' results = duckdb.execute(query).fetchall() has_more = len(results) > limit data = results[:limit] next_cursor = data[-1][0] if has_more else None return {'data': data, 'total': len(data), 'cursor': next_cursor}

7Grille par niveau

NiveauMaitriseSignal GONO-GO
ConfirméMéthodes HTTP, auth API Key/Bearer, pagination basiqueImplémente la pagination correctement, gère les erreurs HTTPNe gère pas le rate limiting, ignore les codes d erreur
SeniorOAuth2, cursor pagination, versioning, retry avec backoffUtilise tenacity pour les retries, implémente cursor pagination, a versionné une APIN a jamais pensé à la dépréciation d une API

Vous recrutez un Data Engineer ou Data Scientist ?

Premier entretien gratuit. Rapport GO/NO-GO sous 48h.