Declare les trois contrats de Zonza
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
332afede0e
commit
54479d9af9
59
contrats.py
Normal file
59
contrats.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""Les trois frontières de Zonza.
|
||||
|
||||
Chaque contrat est délibérément minuscule : ce sont les seuls points par lesquels
|
||||
le noyau parle au monde extérieur. Tout ce qui dépend d'un système d'exploitation,
|
||||
d'une bibliothèque d'affichage ou d'un moteur de transcription vit DERRIÈRE l'un
|
||||
d'eux — jamais dans `core.py`, `controller.py` ni `audio_level.py`.
|
||||
"""
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class Transcripteur(Protocol):
|
||||
"""Transforme un signal audio en texte."""
|
||||
|
||||
def warmup(self) -> None:
|
||||
"""Charge le modèle une fois, pour que la première dictée ne traîne pas."""
|
||||
|
||||
def transcrire(self, audio) -> str:
|
||||
"""Rend le texte d'un tableau numpy float32 mono."""
|
||||
|
||||
|
||||
class Overlay(Protocol):
|
||||
"""La fenêtre flottante affichée pendant la dictée."""
|
||||
|
||||
def show(self) -> None:
|
||||
"""Affiche la fenêtre et démarre l'animation. Thread principal."""
|
||||
|
||||
def hide(self) -> None:
|
||||
"""Masque la fenêtre et arrête l'animation. Thread principal."""
|
||||
|
||||
def set_level(self, level: float) -> None:
|
||||
"""Pousse un niveau audio dans [0, 1]. Appelable depuis un thread de fond."""
|
||||
|
||||
def set_status(self, text: str) -> None:
|
||||
"""Texte affiché dans la bulle. Appelable depuis un thread de fond."""
|
||||
|
||||
def rearmer_garde(self, duree_s: float) -> None:
|
||||
"""Remplace la durée de vie maximale de la fenêtre. Thread principal."""
|
||||
|
||||
|
||||
class Systeme(Protocol):
|
||||
"""Les appels qui diffèrent d'un système d'exploitation à l'autre."""
|
||||
|
||||
def jouer_son(self, nom: str) -> None:
|
||||
"""Joue « start », « done » ou « error » sans bloquer."""
|
||||
|
||||
def modificateurs_enfonces(self) -> bool:
|
||||
"""True si une touche modificatrice est physiquement enfoncée."""
|
||||
|
||||
def raccourci_defaut(self) -> str:
|
||||
"""Raccourci global au format pynput, propre à la plateforme."""
|
||||
|
||||
def chemin_log(self) -> str:
|
||||
"""Chemin absolu du journal."""
|
||||
|
||||
def dossier_donnees(self) -> str:
|
||||
"""Dossier des données de l'application (verrou d'instance, réglages)."""
|
||||
|
||||
def acquerir_verrou_instance(self, chemin: str):
|
||||
"""Verrou exclusif non bloquant ; rend le fichier, ou None s'il est pris."""
|
||||
27
tests/test_contrats.py
Normal file
27
tests/test_contrats.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""Une suite unique, rejouée contre TOUTES les implémentations d'un contrat.
|
||||
|
||||
Le jour où une implémentation oublie une méthode, c'est ici que ça rougit — y
|
||||
compris pour les plateformes absentes de la machine de développement.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
import pytest
|
||||
|
||||
from contrats import Overlay, Systeme, Transcripteur
|
||||
|
||||
|
||||
@pytest.mark.parametrize("contrat, methodes", [
|
||||
(Transcripteur, ["warmup", "transcrire"]),
|
||||
(Overlay, ["show", "hide", "set_level", "set_status", "rearmer_garde"]),
|
||||
(Systeme, ["jouer_son", "modificateurs_enfonces", "raccourci_defaut",
|
||||
"chemin_log", "dossier_donnees", "acquerir_verrou_instance"]),
|
||||
])
|
||||
def test_le_contrat_declare_exactement_ses_methodes(contrat, methodes):
|
||||
declarees = [n for n, _ in inspect.getmembers(contrat, inspect.isfunction)
|
||||
if not n.startswith("_")]
|
||||
assert sorted(declarees) == sorted(methodes)
|
||||
|
||||
|
||||
def test_les_contrats_sont_documentes():
|
||||
for contrat in (Transcripteur, Overlay, Systeme):
|
||||
assert contrat.__doc__, f"{contrat.__name__} doit dire à quoi il sert"
|
||||
Loading…
Reference in New Issue
Block a user