For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale
Theme
Codex

Codex use case

Migration d’un module de validation

Mettez à niveau un seul module et montrez, tests à l’appui, que son comportement public reste identique.

Difficulty Advanced
Time horizon Long-running

Fournissez à Codex un cahier des charges qui délimite la migration, des jeux de données pour les tests de contrat et un environnement cloud prêt à l’emploi. Demandez-lui de mettre à niveau un seul module de validation et de fournir la modification ciblée, les résultats qui démontrent l’équivalence des comportements, les écarts restants et les instructions de retour en arrière.

Best for

  • Équipes de développement qui migrent un ancien composant de validation.
  • Migrations encadrées par des contrats explicites et des tests reproductibles.
  • Équipes qui doivent faire relire une modification de faible ampleur pendant que les autres modules continuent de fonctionner.

Contents

    ← All use cases

    Migration d’un module de validation

    Mettez à niveau un seul module et montrez, tests à l’appui, que son comportement public reste identique.

    Fournissez à Codex un cahier des charges qui délimite la migration, des jeux de données pour les tests de contrat et un environnement cloud prêt à l’emploi. Demandez-lui de mettre à niveau un seul module de validation et de fournir la modification ciblée, les résultats qui démontrent l’équivalence des comportements, les écarts restants et les instructions de retour en arrière.

    Advanced
    Long-running

    Fournissez à Codex un cahier des charges qui délimite la migration, des jeux de données pour les tests de contrat et un environnement cloud prêt à l’emploi. Demandez-lui de mettre à niveau un seul module de validation et de fournir la modification ciblée, les résultats qui démontrent l’équivalence des comportements, les écarts restants et les instructions de retour en arrière.

    Advanced
    Long-running

    Best for

    • Équipes de développement qui migrent un ancien composant de validation.
    • Migrations encadrées par des contrats explicites et des tests reproductibles.
    • Équipes qui doivent faire relire une modification de faible ampleur pendant que les autres modules continuent de fonctionner.

    Starter prompt

    Dans cet environnement cloud, migrez le module [module] du dépôt [repository] de la version [current pinned version] vers la version [approved target version]. Consultez AGENTS.md, [migration brief], [official migration guide], le fichier de verrouillage des dépendances, le code qui appelle le module et [contract test locations]. Consignez le commit de départ, la référence de l’environnement et de sa configuration, la version de Python installée, les versions du framework, de Pydantic et de la v1 embarquée, ainsi que l’empreinte du fichier de verrouillage. Vérifiez la compatibilité entre la version de départ et la version cible. Regroupez les préparatifs qui touchent aux dépendances ou aux imports partagés dans une modification distincte et faites-la relire. Commencez par exécuter [baseline commands], puis recensez les éléments à migrer et rédigez un plan succinct dans [plan file]. Préservez les contrats publics [public contracts]. Laissez les modules [out-of-scope modules] inchangés. Ne supprimez du code obsolète que dans le périmètre approuvé et indiquez le nom de chaque adaptateur de compatibilité à conserver. Si vous pouvez isoler la partie à migrer et que l’état de référence est exploitable, exécutez les mêmes tests de contrat HTTP avant et après la migration, sans les modifier. Ces tests doivent passer par le routeur, le gestionnaire d’erreurs et le mécanisme de sérialisation des réponses réellement utilisés par l’application. Comparez les codes de statut HTTP, les en-têtes prévus par le contrat, les entrées acceptées ou rejetées, les clés et les types des données JSON publiques, les valeurs par défaut, les valeurs nulles et le contenu intégral des réponses d’erreur, y compris leur structure. Exécutez aussi les tests d’intégration et les vérifications de lint et de types concernés. Si la migration exige des mises à niveau plus larges ou une modification du comportement public, signalez la décision à prendre avant d’élargir le périmètre. N’approuvez aucun nouvel instantané de test, ne masquez aucun écart de comportement public par normalisation et n’affaiblissez aucune assertion. Pour chaque vérification, consignez les révisions, l’environnement, les versions, les commandes exécutées avant et après la migration, les codes de sortie et les chemins d’accès aux fichiers qui attestent des résultats. Indiquez si la vérification a réussi, a échoué, a été bloquée ou n’a pas été exécutée, et précisez ses limites. Fournissez le diff, les résultats des vérifications avant et après la migration, les écarts non résolus, les modifications apportées aux dépendances et au fichier de verrouillage, les instructions de retour en arrière et une proposition de description de pull request. Créez une pull request en brouillon uniquement si les outils connectés au dépôt et mes autorisations le permettent. Sinon, fournissez le patch et la description. Ne fusionnez ni ne déployez les modifications, ne modifiez pas les données de production et n’entamez aucune autre partie de la migration.
    Dans cet environnement cloud, migrez le module [module] du dépôt [repository] de la version [current pinned version] vers la version [approved target version]. Consultez AGENTS.md, [migration brief], [official migration guide], le fichier de verrouillage des dépendances, le code qui appelle le module et [contract test locations]. Consignez le commit de départ, la référence de l’environnement et de sa configuration, la version de Python installée, les versions du framework, de Pydantic et de la v1 embarquée, ainsi que l’empreinte du fichier de verrouillage. Vérifiez la compatibilité entre la version de départ et la version cible. Regroupez les préparatifs qui touchent aux dépendances ou aux imports partagés dans une modification distincte et faites-la relire. Commencez par exécuter [baseline commands], puis recensez les éléments à migrer et rédigez un plan succinct dans [plan file]. Préservez les contrats publics [public contracts]. Laissez les modules [out-of-scope modules] inchangés. Ne supprimez du code obsolète que dans le périmètre approuvé et indiquez le nom de chaque adaptateur de compatibilité à conserver. Si vous pouvez isoler la partie à migrer et que l’état de référence est exploitable, exécutez les mêmes tests de contrat HTTP avant et après la migration, sans les modifier. Ces tests doivent passer par le routeur, le gestionnaire d’erreurs et le mécanisme de sérialisation des réponses réellement utilisés par l’application. Comparez les codes de statut HTTP, les en-têtes prévus par le contrat, les entrées acceptées ou rejetées, les clés et les types des données JSON publiques, les valeurs par défaut, les valeurs nulles et le contenu intégral des réponses d’erreur, y compris leur structure. Exécutez aussi les tests d’intégration et les vérifications de lint et de types concernés. Si la migration exige des mises à niveau plus larges ou une modification du comportement public, signalez la décision à prendre avant d’élargir le périmètre. N’approuvez aucun nouvel instantané de test, ne masquez aucun écart de comportement public par normalisation et n’affaiblissez aucune assertion. Pour chaque vérification, consignez les révisions, l’environnement, les versions, les commandes exécutées avant et après la migration, les codes de sortie et les chemins d’accès aux fichiers qui attestent des résultats. Indiquez si la vérification a réussi, a échoué, a été bloquée ou n’a pas été exécutée, et précisez ses limites. Fournissez le diff, les résultats des vérifications avant et après la migration, les écarts non résolus, les modifications apportées aux dépendances et au fichier de verrouillage, les instructions de retour en arrière et une proposition de description de pull request. Créez une pull request en brouillon uniquement si les outils connectés au dépôt et mes autorisations le permettent. Sinon, fournissez le patch et la description. Ne fusionnez ni ne déployez les modifications, ne modifiez pas les données de production et n’entamez aucune autre partie de la migration.

    Quand utiliser ce workflow

    Utilisez ce workflow lorsque le périmètre de la migration est bien défini et que des tests permettent de comparer le comportement avant et après la migration. Fournissez à Codex les consignes de migration, le module et le code qui l’appelle, des jeux de données de test figés pour vérifier le contrat, ainsi qu’un environnement cloud prêt à l’emploi. Demandez-lui une modification ciblée, accompagnée des résultats de vérification et d’une procédure de retour en arrière.

    L’exemple fait passer un seul modèle de validation d’un service de gestion des commandes de Pydantic v1 à la v2. Il comprend l’implémentation d’origine, une étape de préparation qui assure la compatibilité et le module final. Les tests couvrent le routeur FastAPI utilisé par le service, le gestionnaire d’erreurs, la sérialisation des réponses, la route voisine restée en v1 et le document OpenAPI public. L’exemple ne comprend ni migration de base de données ni déploiement.

    Préparez l’environnement cloud

    1. Choisissez Travailler dans > Cloud, puis sélectionnez un environnement publié.

    2. Pour en créer un, sélectionnez Créer un environnement, choisissez les dépôts GitHub et demandez à Codex de préparer l’environnement d’exécution, les dépendances et les services de test isolés.

    3. Consultez le rapport de configuration, corrigez les problèmes signalés par les vérifications, puis sélectionnez Publier avant de lancer une nouvelle tâche.

    Si les dépendances ou la configuration des services changent, modifiez l’environnement réutilisable, testez-le, puis sélectionnez Republier.

    Validez l’environnement mis à jour dans une nouvelle tâche. L’actualisation du dépôt conserve les caches de dépendances sans relancer les commandes d’installation ou de démarrage. Les tâches existantes conservent leur propre état.

    Configurez séparément les destinations réseau et les identifiants nécessaires.

    À chaque étape, consignez la référence de l’environnement et de sa configuration, le commit du code source, la version de Python jusqu’au numéro de correctif, les versions du framework et des dépendances, la version du gestionnaire de paquets et l’empreinte du fichier de verrouillage. Vérifiez ces versions dans la nouvelle tâche avant d’en utiliser les résultats.

    Exécutez l’exemple

    Téléchargez l’exemple complet de migration. Il comprend trois projets uv autonomes, leurs fichiers uv.lock avec les versions résolues, les implémentations de l’application, un adaptateur commun pour les erreurs et le schéma, des jeux de test HTTP figés et le script de vérification du contrat.

    Utilisez Python 3.11, 3.12 ou 3.13 et installez uv, puis exécutez les commandes suivantes :

    unzip sample.zip
    cd migrate-a-validation-module
    uv run --project support/target --python python3 --frozen python run.py --compare-stages --output evidence

    Le script de comparaison crée un .venv distinct pour chaque étape et installe les dépendances avec uv sync --frozen. Toutes les étapes utilisent l’interpréteur Python qui a lancé le script et les mêmes jeux de test pour HTTP et le schéma. Les versions des dépendances restent verrouillées.

    Python 3.14 et les versions ultérieures sont exclus, car la couche de compatibilité avec Pydantic v1 ne les prend pas en charge. Si l’exécution réussit, elle se termine par :

    PASS: legacy, prepared, target HTTP/schema/serialization contracts match
    Native model schemas differ; the existing public schema adapter is preserved.

    Consultez evidence/legacy.json, prepared.json, target.json et summary.json. Ces fichiers contiennent les réponses observées, les versions de l’environnement d’exécution, les empreintes du code source, des jeux de test et des fichiers de verrouillage, les commandes, les codes de sortie et les chemins des journaux bruts.

    Le script teste les points de terminaison HTTP de l’application d’exemple via FastAPI et son TestClient, dans le même processus. Exécutez-le dans la tâche dans le cloud de votre choix et conservez les résultats de vérification de cette tâche.

    Exécutez python3 run.py sans --compare-stages uniquement pour vérifier le modèle cible dans un environnement où Pydantic v2 est installé. Ce mode autonome vérifie sept cas de validation et de sérialisation. Il ne teste pas les échanges HTTP et ne compare pas les résultats entre les étapes. Pour vérifier une migration, utilisez la commande de comparaison complète ci-dessus.

    La comparaison complète vérifie 23 cas HTTP à chaque étape, ainsi que le document OpenAPI public et la sérialisation des réponses. Elle a réussi en local avec Python 3.11, 3.12 et 3.13. Le fichier README.md de l’archive indique les versions testées et l’historique des exécutions. Exécutez la comparaison actuelle dans votre environnement cloud pour y consigner les résultats.

    Distinguez les trois configurations de dépendances

    Étape Python / FastAPI / Pydantic Périmètre de la migration
    Version d’origine 3.11–3.13 / 0.119.0 / 1.10.22 Les modèles existants effectuent leurs imports depuis pydantic.
    Version de référence préparée 3.11–3.13 / 0.119.0 / 2.12.3 Les deux modèles utilisent pydantic.v1. Consignez le numéro de version exact de la v1 fournie.
    Version cible 3.11–3.13 / 0.119.0 / 2.12.3 Seul OrderInput passe à la v2. La route voisine conserve son propre modèle v1.

    Les versions préparée et cible ont des fichiers de verrouillage des dépendances identiques. La migration du module modifie le modèle et la méthode qui le sérialise directement, sans changer les dépendances de l’environnement.

    L’étape de préparation fait l’objet d’une modification distincte, soumise à une revue de code. Elle adapte aussi les routes qui restent en v1 : avant la validation de la réponse, leurs adaptateurs produisent avec dict(by_alias=True) des dictionnaires dont les clés sont les alias. Renvoyer directement le modèle v1 dans un environnement v2 risque de faire disparaître les alias publics.

    Dans la version cible, l’adaptateur des commandes utilise model_dump(by_alias=True), et la route voisine en v1 conserve l’adaptateur introduit lors de la préparation.

    Le guide de migration de FastAPI précise que seules les versions 0.119.0 à 0.127.x prennent temporairement en charge la coexistence de routes v1 et v2. La version 0.128.0 a supprimé cette prise en charge.

    Cet exemple fixe délibérément la version à 0.119.0 pour illustrer cette transition. Pour une application FastAPI plus récente, utilisez exclusivement la v2 dans le périmètre de la migration. Désignez un responsable de la couche de compatibilité temporaire et précisez à quelle condition elle devra être supprimée. N’imbriquez pas de modèles v1 et v2. Cette couche de compatibilité ne permet pas de migrer vers Python 3.14.

    Examinez les tests de contrat

    Les cas HTTP figés couvrent les requêtes valides, les notes omises ou explicitement définies à null, les identifiants absents ou invalides, la présence de plusieurs champs inconnus, les combinaisons d’erreurs de champs, les notes invalides, le JSON mal formé, les types de contenu incorrects, l’absence de corps de requête, les alias, les corps de requête de type tableau ou chaîne, ainsi que la route voisine restée en v1. Les tests comparent les codes de statut, l’en-tête Content-Type et l’intégralité des réponses JSON après analyse, sans supprimer de champs d’erreur ni modifier les types attendus.

    Depuis la version d’origine, l’application définit elle-même le format de ses réponses d’erreur et son document OpenAPI public. Les adaptateurs d’erreurs et de schéma restent en place. La version cible conserve l’ordre des erreurs d’origine : d’abord les erreurs portant sur les champs déclarés, puis les clés inconnues par ordre alphabétique.

    Les tests comparent le tableau error.fields dans son intégralité et dans l’ordre reçu, sans trier les réponses observées. Le script consigne séparément le schéma natif de chaque modèle et vérifie que ces schémas diffèrent. L’équivalence du schéma public n’implique pas que les schémas générés par la dépendance soient identiques.

    Le service conserve le comportement d’origine pour les entrées : il accepte comme objet un tableau de paires tel que [["order_id", "ord_123"]]. Le tableau [] et une chaîne JSON vide sont tous deux convertis en objets vides et déclenchent une erreur d’identifiant manquant. Une chaîne non vide reste un corps de requête invalide. Le modèle cible continue d’accepter les valeurs que dict(value) parvient à convertir. Pour limiter les entrées aux seuls objets, le responsable du contrat doit approuver ce changement. Modifier le résultat attendu du test ne suffit pas.

    Dans votre dépôt, figez les réponses effectivement renvoyées avant de modifier le code. Conservez les champs facultatifs avec leurs valeurs par défaut explicites, le degré de rigueur de la validation et le traitement des champs inconnus. Testez le gestionnaire d’erreurs utilisé par l’application et la sérialisation des réponses.

    Les erreurs de validation brutes diffèrent selon les versions. Le guide Pydantic : migration vers la v2 décrit aussi les changements concernant les alias, la conversion implicite des types, les champs facultatifs et les fonctions de validation personnalisées. La v2 ne convertit pas les exceptions TypeError levées dans ces fonctions en ValidationError.

    Examinez le script de vérification du contrat

    Cet extrait provient du fichier run.py de l’archive. Après avoir extrait le ZIP, exécutez les fichiers complets. L’archive contient les configurations de chaque étape avec leurs dépendances verrouillées, ainsi que les jeux de test HTTP.

    Comparez les trois étapes
    class OrderInput(BaseModel):
        internal_order_id: StrictStr = Field(alias="order_id")
        customer_note: StrictStr | None = None
        model_config = ConfigDict(extra="forbid", populate_by_name=False)
    
        @model_validator(mode="before")
        @classmethod
        def preserve_legacy_mapping(cls, value):
            # v1 tries dict(value), so pairs, [] and "" can become objects.
            # Preserve successful conversions without changing failed-input errors.
            if not isinstance(value, dict):
                try:
                    return dict(value)
                except (TypeError, ValueError):
                    return value
            return value
    
    
    def serialize_order(payload):
        return OrderInput(**payload).model_dump(by_alias=True)
    
    
    def preserve_legacy_error_order(fields):
        # v1 reports declared-field errors first and unknown keys alphabetically.
        # v2 keeps unknown keys in input order; preserve the existing HTTP contract.
        known = [field for field in fields if field["code"] != "extra_forbidden"]
        extra = sorted(
            (field for field in fields if field["code"] == "extra_forbidden"),
            key=lambda field: field["path"],
        )
        return known + extra
    
    
    def check_target_model():
        for payload, expected in (
            ({"order_id": "ord_123"}, {"order_id": "ord_123", "customer_note": None}),
            (
                {"order_id": "ord_123", "customer_note": "Leave at desk"},
                {"order_id": "ord_123", "customer_note": "Leave at desk"},
            ),
            (
                {"order_id": "ord_123", "customer_note": None},
                {"order_id": "ord_123", "customer_note": None},
            ),
        ):
            assert serialize_order(payload) == expected
        for payload, expected in (
            ({}, [("order_id", "missing")]),
            ({"order_id": 123}, [("order_id", "string_type")]),
            (
                {"order_id": "ord_123", "customer_note": 17},
                [("customer_note", "string_type")],
            ),
            (
                {"order_id": "ord_123", "unexpected": True},
                [("unexpected", "extra_forbidden")],
            ),
        ):
            try:
                OrderInput(**payload)
            except ValidationError as error:
                assert [
                    (item["loc"][0], item["type"]) for item in error.errors()
                ] == expected
            else:
                raise AssertionError(f"Accepted invalid order: {payload}")
        print("PASS: target model validation and serialization (7 cases)")
        print("Model-only checks; use --compare-stages for HTTP migration parity.")
    
    
    def main():
        parser = argparse.ArgumentParser()
        parser.add_argument("--output", type=Path, default=ROOT / "evidence")
        parser.add_argument("--compare-stages", action="store_true")
        args = parser.parse_args()
        if not args.compare_stages:
            check_target_model()
            return
        args.output.mkdir(parents=True, exist_ok=True)
        reports = []
        commands = []
        for stage in ("legacy", "prepared", "target"):
            project = Path("support") / stage
            stage_commands = [
                [
                    "uv",
                    "sync",
                    "--project",
                    str(project),
                    "--python",
                    sys.executable,
                    "--frozen",
                ],
                [
                    "uv",
                    "run",
                    "--project",
                    str(project),
                    "--python",
                    sys.executable,
                    "--frozen",
                    "python",
                    "test/run_stage.py",
                    stage,
                    str(args.output / f"{stage}.json"),
                ],
            ]
            for command in stage_commands:
                completed = subprocess.run(
                    command, cwd=ROOT, capture_output=True, text=True
                )
                label = f"{stage}-{len(commands)}"
                (args.output / f"{label}.log").write_text(
                    completed.stdout + completed.stderr
                )
                commands.append(
                    {
                        "command": command,
                        "exit_code": completed.returncode,
                        "log": f"{label}.log",
                    }
                )
                (args.output / "commands.json").write_text(
                    json.dumps(commands, indent=2, sort_keys=True) + "\n"
                )
                if completed.returncode:
                    print(completed.stdout + completed.stderr, file=sys.stderr)
                    raise SystemExit(completed.returncode)
                print(completed.stdout, end="")
            reports.append(json.loads((args.output / f"{stage}.json").read_text()))
        assert all(report["python"] == platform.python_version() for report in reports)
        for report in reports[1:]:
            for key in (
                "observations",
                "public_openapi",
                "direct_call",
                "fixture_sha256",
                "schema_fixture_sha256",
            ):
                assert report[key] == reports[0][key], f"Parity failed: {key}"
        assert reports[1]["lock_sha256"] == reports[2]["lock_sha256"]
        native_schema_differs = (
            reports[0]["native_model_schema"] != reports[2]["native_model_schema"]
        )
        assert native_schema_differs, "The example should expose native schema drift"
        manifest = {
            str(path.relative_to(ROOT)): digest(path)
            for path in sorted(ROOT.rglob("*"))
            if path.is_file()
            and not path.resolve().is_relative_to(args.output.resolve())
            and not any(
                part in {".venv", "__pycache__", "evidence", ".ruff_cache"}
                for part in path.relative_to(ROOT).parts
            )
        }
        summary = {
            "status": "pass",
            "scope": "synthetic in-process FastAPI TestClient execution",
            "stages": [report["stage"] for report in reports],
            "http_cases_per_stage": reports[0]["http_cases"],
            "native_model_schema_differs": native_schema_differs,
            "public_http_and_schema_parity": True,
            "commands": commands,
            "source_sha256": manifest,
        }
        (args.output / "summary.json").write_text(
            json.dumps(summary, indent=2, sort_keys=True) + "\n"
        )
        print("PASS: legacy, prepared, target HTTP/schema/serialization contracts match")
        print(
            "Native model schemas differ; the existing public schema adapter is preserved."
        )

    Exécutez le workflow dans votre dépôt

    1. Rédigez les consignes de migration. Indiquez le module, le code qui l’appelle directement, le responsable, les versions approuvées, les tests de contrat, le code hors périmètre et la procédure de retour en arrière. Arrêtez-vous si la migration de ce module exige une mise à niveau plus large des dépendances ou modifie le comportement public.
    2. Établissez la référence de départ. Demandez à Codex de lire AGENTS.md, les consignes de migration, les recommandations officielles, les fichiers de verrouillage, le code appelant et les tests. Consignez le commit de départ et les versions installées. Si des vérifications échouent dès le départ, expliquez ces échecs ou corrigez-en la cause avant de conclure à l’équivalence des résultats.
    3. Préparez la compatibilité séparément. Si des dépendances partagées lient le module à d’autres parties du code, examinez d’abord les modifications de préparation. Vérifiez que les tests réussissent avec les mêmes données sur la version d’origine et sur la version de référence préparée.
    4. Migrez un seul module. Utilisez le prompt de départ proposé sur cette page. Modifiez le module et son adaptateur direct. Préservez les modules hors périmètre et indiquez quel code de compatibilité subsiste.
    5. Exécutez les mêmes tests. Comparez les résultats de la version de référence et de la version finale avec les mêmes jeux de test HTTP et de schéma, sans modifier les résultats attendus. Exécutez les tests d’intégration et les vérifications de lint et de types concernés. Enregistrez leurs sorties en précisant l’état final du code source et des dépendances auquel elles correspondent.
    6. Vérifiez le dossier de revue. Examinez le diff, les résultats observés, les écarts non résolus, la couche de compatibilité restante et la procédure de retour en arrière. Créez une pull request en brouillon uniquement si les outils et les permissions du dépôt le permettent. Laissez à la personne chargée de la revue le soin de fusionner les modifications, de les déployer et d’engager la migration d’un autre module.

    Consignez les vérifications effectuées

    Pour chaque critère, consignez les éléments de vérification suivants :

    Criterion: [behavior and test]
    Environment: [setup reference; actual runtime and dependency versions]
    Before: [commit/diff ID; command; exit code; observed result; evidence path]
    After: [final commit/diff ID; command; exit code; observed result; evidence path]
    Status: [pass / fail / blocked / not run]
    Limits: [untested behavior, missing prerequisite, or required owner decision]

    Un import réussi, une vérification de lint réussie ou un instantané régénéré ne suffisent pas à établir que l’API conserve le même comportement. Consignez séparément les différences intentionnelles et relancez les vérifications si le code source final change. Lorsque vous adaptez l’exemple, incluez dans les tests de contrat les services de votre application et le code appelant.

    Related use cases