Aller au contenu
Mohamed Tahiri
Miami --:--
2023 → · open source

SDK, Alexa et Home Assistant

Une plateforme associative publie chaque jour les horaires de milliers de lieux. J’ai écrit le contrat de son API, généré deux SDK typés à 100 % de couverture, repris et réarchitecturé sa skill Alexa, et porté son intégration Home Assistant.

Rôle
Auteur des SDK, lead Alexa
Clients
Python, TypeScript, Alexa, Home Assistant
Usage
~3 300 téléchargements / mois
Qualité
1 300+ tests, 100 % de couverture
clients pour une même API
4
téléchargements des SDK par mois
~3 300
de couverture, lignes et branches
100 %
tests sur les quatre projets
1 300+

Le contexte

Une plateforme associative publie les horaires quotidiens de milliers de lieux communautaires. Chaque lieu tient son planning annuel, affiché sur des écrans sur place, dans une app mobile, et de plus en plus sur des enceintes et dans la maison connectée. L’API existait, mais sans documentation ni bibliothèque officielle : chaque client refaisait les mêmes calculs, avec les mêmes bugs.

Un contrat, quatre clients

APIPHP · SYMFONYContrôles en CISPECTRAL · AJV · PROD LA NUITSpec OpenAPI 3.1ÉCRITE À LA MAIN · VERSIONNÉEGénérateur PythonJINJA2 · RUFFGénérateur TSTEMPLATES · BIOMESDK PythonPYPI · SYNC + ASYNCSDK TypeScriptNPM · ZÉRO DÉPENDANCEHome AssistantHACS · CŒUR EN REVUESkill AlexaLAMBDA · 7 LOCALESCAPTUREÉPINGLÉE · SHA-256LA CI RÉGÉNÈRE ET ÉCHOUE AU MOINDRE ÉCART

Tout part d’une spec OpenAPI 3.1 que j’ai écrite à la main à partir du code du backend. Elle ne décrit que ce que les clients utilisent, mais elle le décrit exactement. Deux générateurs maison en tirent un SDK Python et un SDK TypeScript, qui alimentent à leur tour l’intégration Home Assistant et la skill Alexa.

Ce qui était difficile

Un contrat écrit à la main qui ne ment pas

Une spec écrite à la main dérive au premier changement du backend. Trois filets l’en empêchent. Des règles Spectral maison refusent toute opération sans identifiant ni description, puisque ces descriptions deviennent la documentation des SDK. AJV valide de vraies réponses capturées en fermant chaque objet (additionalProperties: false injecté partout) : un champ non documenté fait échouer la CI. Et chaque nuit, un job capture des réponses fraîches en production et les revalide.

Côté SDK, la spec est épinglée par tag et par empreinte SHA-256. La CI régénère le code et échoue au moindre écart : impossible de publier une bibliothèque qui ne correspond pas au contrat.

Des SDK générés, mais pas génériques

Je n’ai pas pris openapi-generator : un SDK générique, ça se sent. Mes générateurs (Jinja2 et ruff côté Python, templates et Biome côté TypeScript) produisent les types, les ressources et le client. Le transport, les erreurs et les calculs d’horaires restent écrits à la main. Et ce qu’OpenAPI ne sait pas exprimer devient un type :

search.test-d.ts
// simplified: OpenAPI cannot say "a position OR words", the generated union can
client.venues.search({ lat: 48.85, lon: 2.35 }) // ok
client.venues.search({ words: 'paris' })        // ok

// @ts-expect-error a latitude needs its longitude
client.venues.search({ lat: 48.85 })

Le client reprend les erreurs avec backoff et jitter, respecte Retry-After jusqu’à 60 secondes, s’annule proprement avec AbortSignal, et ne suit jamais une redirection, pour que le jeton ne parte pas vers un autre domaine.

L’heure, le vrai ennemi

Chaque lieu publie des heures murales, « 05:42 », jour par jour, dans son fuseau. Les clients ont besoin d’instants. Entre les deux : les changements d’heure, les événements après minuit, les décalages relatifs et les saisies invalides. Python s’appuie sur zoneinfo, TypeScript sur Intl seul, sans dépendance. Une heure invalide devient null plutôt qu’une fausse valeur, et un test de référence vérifie que les deux SDK calculent le même calendrier lunaire, mois par mois, de 1990 à 2060.

Un son à la minute près, ou pas du tout

J’ai repris une skill Alexa écrite par un prestataire et je l’ai réarchitecturée : passage de JavaScript à TypeScript strict en treize PR, données dans DynamoDB, et surtout des annonces sonores programmées qui doivent sonner à l’heure, dans le fuseau de chaque lieu.

Planificateur1 ENTRÉE PAR LIEU × ÉVÉNEMENTRafraîchissement04:05 HEURE LOCALEDéclencheurLAMBDA · FAN-OUTFile SQSVISIBILITÉ 30 SFile d’erreursALARME · JAMAIS REJOUÉEWorkerJETTE > 5 MIN DE RETARDDynamoDBJETONS · APPAREILSAlexaROUTINE → ENCEINTEAPRÈS 3 ESSAISEN RETARD, C’EST PIRE QUE JAMAIS

Une annonce en retard est pire qu’une annonce manquée. Au-delà de cinq minutes, le worker jette le message. La visibilité SQS est réduite à 30 secondes pour que trois essais tiennent dans la fenêtre, et la file d’erreurs déclenche une alarme mais n’est jamais rejouée. Les écritures concurrentes (la voix, le site, un widget) ne modifient que leurs champs, sous un numéro de version, et rejouent leur diff en cas de conflit.

Le niveau d’exigence

100 % de couverture, imposée

Lignes et branches, dans les deux SDK. mypy et pyright en strict, API publique typée à 100 %, Python 3.10 à 3.14, Node 22 à 26, Deno et Bun testés.

Des publications sans secret

PyPI et npm via OIDC, avec provenance. Actions épinglées par SHA, workflows audités, aucun jeton longue durée.

Deux personnes pour la prod

Côté Alexa, déployer en production exige un tag et l’accord de quelqu’un d’autre que celui qui lance. Le rôle de déploiement ne peut que mettre à jour du code.

Home Assistant

L’intégration communautaire est celle qui tourne chez les gens : j’ai écrit 60 de ses 74 PR fusionnées, avec 394 tests, 100 % de couverture et une interface en six langues. Une version pour le cœur de Home Assistant, coécrite, est en revue chez les mainteneurs. Chaque entité y planifie son prochain changement de valeur au lieu d’interroger l’API toutes les minutes.

Ce que j’en retiens

Un bon contrat vaut plus que n’importe quel générateur, et le temps est le domaine le plus traître que je connaisse. Le reste, c’est de l’outillage, pour que plus personne n’ait à y penser.

Stack

OpenAPI 3.1PythonTypeScriptSpectralAJVpytestVitestAWS LambdaDynamoDBEventBridgeSQSAlexa Skills KitHome AssistantGitHub ActionsSentry
Projet suivantBuddy →
Contact

Construisons quelque chose.

Ou laissez un message