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

Codex use case

Migrar un módulo de validación

Actualiza un módulo y demuestra que conserva su comportamiento público.

Difficulty Advanced
Time horizon Long-running

Dale a Codex una especificación de migración con un alcance definido, fixtures para las pruebas de contrato y un entorno en la nube listo para usar. Actualiza un solo módulo de validación y entrega el cambio, los resultados que demuestran qué comportamiento se conserva, las diferencias pendientes y las instrucciones para revertir el cambio.

Best for

  • Equipos de ingeniería que migran un componente de validación heredado.
  • Migraciones con contratos explícitos y pruebas repetibles.
  • Equipos que necesitan un cambio pequeño y revisado mientras los demás módulos siguen funcionando.

Contents

    ← All use cases

    Migrar un módulo de validación

    Actualiza un módulo y demuestra que conserva su comportamiento público.

    Dale a Codex una especificación de migración con un alcance definido, fixtures para las pruebas de contrato y un entorno en la nube listo para usar. Actualiza un solo módulo de validación y entrega el cambio, los resultados que demuestran qué comportamiento se conserva, las diferencias pendientes y las instrucciones para revertir el cambio.

    Advanced
    Long-running

    Dale a Codex una especificación de migración con un alcance definido, fixtures para las pruebas de contrato y un entorno en la nube listo para usar. Actualiza un solo módulo de validación y entrega el cambio, los resultados que demuestran qué comportamiento se conserva, las diferencias pendientes y las instrucciones para revertir el cambio.

    Advanced
    Long-running

    Best for

    • Equipos de ingeniería que migran un componente de validación heredado.
    • Migraciones con contratos explícitos y pruebas repetibles.
    • Equipos que necesitan un cambio pequeño y revisado mientras los demás módulos siguen funcionando.

    Starter prompt

    Migra [module] en [repository] de [current pinned version] a [approved target version] en este entorno en la nube. Lee AGENTS.md, [migration brief], [official migration guide], el archivo de bloqueo de dependencias, el código que llama al módulo y [contract test locations]. Registra el commit inicial, la referencia del entorno y su configuración, la versión de Python instalada, las versiones del framework, de Pydantic y de bundled-v1, y el hash del archivo de bloqueo. Confirma la compatibilidad entre la versión inicial y la de destino. Trata la preparación de las dependencias y las importaciones compartidas como un cambio que se revisa por separado. Primero ejecuta [baseline commands], enumera los elementos que abarca la migración y escribe un plan breve en [plan file]. Conserva [public contracts]. Deja [out-of-scope modules] sin cambios. Elimina código obsoleto solo dentro del alcance aprobado e indica el nombre de cada adaptador de compatibilidad que deba conservarse. Si puedes aislar la parte que vas a migrar y el estado inicial permite ejecutar las pruebas, ejecuta las mismas pruebas de contrato HTTP antes y después del cambio, sin modificarlas. En ambas ejecuciones, usa el enrutador, el manejador de errores y el mecanismo de serialización de respuestas que usa el módulo. Compara los códigos de estado, los encabezados definidos por el contrato, las entradas aceptadas y rechazadas, las claves públicas del JSON y sus tipos, los valores predeterminados y nulos, y la estructura y el contenido completos de las respuestas de error. Ejecuta también las pruebas de integración y las comprobaciones de lint y de tipos afectadas por el cambio. Si hacen falta actualizaciones más amplias o cambios en el comportamiento público, comunícalo antes de ampliar el alcance. No apruebes nuevas instantáneas, no ocultes diferencias de comportamiento público mediante la normalización ni debilites las aserciones. Para cada comprobación, registra las revisiones, el entorno, las versiones, los comandos ejecutados antes y después del cambio, los códigos de salida y las rutas de los archivos de evidencia. Indica si la comprobación pasó, falló, quedó bloqueada o no se ejecutó, y señala sus limitaciones. Entrega el diff, la evidencia de antes y después del cambio, las diferencias sin resolver, los cambios en las dependencias y en el archivo de bloqueo, las instrucciones para revertir el cambio y un borrador de la descripción del Pull Request. Crea un Pull Request en borrador solo si las herramientas conectadas al repositorio y mis permisos lo permiten. De lo contrario, entrega el parche y la descripción. No fusiones ni despliegues el cambio, no modifiques datos de producción ni empieces a migrar otra parte.
    Migra [module] en [repository] de [current pinned version] a [approved target version] en este entorno en la nube. Lee AGENTS.md, [migration brief], [official migration guide], el archivo de bloqueo de dependencias, el código que llama al módulo y [contract test locations]. Registra el commit inicial, la referencia del entorno y su configuración, la versión de Python instalada, las versiones del framework, de Pydantic y de bundled-v1, y el hash del archivo de bloqueo. Confirma la compatibilidad entre la versión inicial y la de destino. Trata la preparación de las dependencias y las importaciones compartidas como un cambio que se revisa por separado. Primero ejecuta [baseline commands], enumera los elementos que abarca la migración y escribe un plan breve en [plan file]. Conserva [public contracts]. Deja [out-of-scope modules] sin cambios. Elimina código obsoleto solo dentro del alcance aprobado e indica el nombre de cada adaptador de compatibilidad que deba conservarse. Si puedes aislar la parte que vas a migrar y el estado inicial permite ejecutar las pruebas, ejecuta las mismas pruebas de contrato HTTP antes y después del cambio, sin modificarlas. En ambas ejecuciones, usa el enrutador, el manejador de errores y el mecanismo de serialización de respuestas que usa el módulo. Compara los códigos de estado, los encabezados definidos por el contrato, las entradas aceptadas y rechazadas, las claves públicas del JSON y sus tipos, los valores predeterminados y nulos, y la estructura y el contenido completos de las respuestas de error. Ejecuta también las pruebas de integración y las comprobaciones de lint y de tipos afectadas por el cambio. Si hacen falta actualizaciones más amplias o cambios en el comportamiento público, comunícalo antes de ampliar el alcance. No apruebes nuevas instantáneas, no ocultes diferencias de comportamiento público mediante la normalización ni debilites las aserciones. Para cada comprobación, registra las revisiones, el entorno, las versiones, los comandos ejecutados antes y después del cambio, los códigos de salida y las rutas de los archivos de evidencia. Indica si la comprobación pasó, falló, quedó bloqueada o no se ejecutó, y señala sus limitaciones. Entrega el diff, la evidencia de antes y después del cambio, las diferencias sin resolver, los cambios en las dependencias y en el archivo de bloqueo, las instrucciones para revertir el cambio y un borrador de la descripción del Pull Request. Crea un Pull Request en borrador solo si las herramientas conectadas al repositorio y mis permisos lo permiten. De lo contrario, entrega el parche y la descripción. No fusiones ni despliegues el cambio, no modifiques datos de producción ni empieces a migrar otra parte.

    Cuándo usar este flujo de trabajo

    Usa este flujo de trabajo cuando la migración tenga un alcance bien definido y cuentes con pruebas que comparen el comportamiento anterior con el nuevo. Dale a Codex las especificaciones de la migración, el módulo y el código que lo invoca, los fixtures de contrato con valores fijos y un entorno en la nube preparado. Pídele un solo cambio acotado, con evidencia e instrucciones para revertirlo.

    En el ejemplo se migra un modelo de validación de un servicio de pedidos de Pydantic v1 a v2. El ejemplo incluye la implementación original, una etapa de preparación para asegurar la compatibilidad y el módulo final. Las pruebas cubren el enrutador real de FastAPI, el manejador de errores, la serialización de respuestas, la ruta heredada adyacente y el documento público de OpenAPI. El ejemplo no incluye ninguna migración de base de datos ni ningún despliegue.

    Prepara el entorno en la nube

    1. Elige Trabajar en > Nube y selecciona un entorno publicado.

    2. Para crear uno, selecciona Crear entorno, elige los repositorios de GitHub y pídele a Codex que prepare el entorno de ejecución, las dependencias y los servicios de prueba aislados.

    3. Revisa el informe de configuración, corrige los problemas detectados en las comprobaciones y selecciona Publicar antes de iniciar una tarea nueva.

    Si cambian las dependencias o la configuración de los servicios, edita el entorno reutilizable, pruébalo y selecciona Volver a publicar.

    Valida el entorno actualizado en una tarea nueva. Al actualizar el repositorio, se conservan las cachés de dependencias sin volver a ejecutar los comandos de instalación o inicio. Las tareas existentes conservan su propio estado.

    Configura por separado los destinos de red y las credenciales que necesites.

    En cada etapa, registra la referencia del entorno y su configuración, el commit del código fuente, la versión de Python con su número de parche, las versiones del framework y de las dependencias, la versión del gestor de paquetes y el hash del archivo de bloqueo. Verifica esas versiones dentro de la tarea nueva antes de usar sus resultados.

    Ejecuta el ejemplo

    Descarga el ejemplo completo de migración. Incluye tres proyectos independientes de uv, sus archivos uv.lock con las dependencias resueltas, las implementaciones de la aplicación, un adaptador compartido para errores y esquemas, fixtures HTTP con valores fijos y el ejecutor de pruebas de contrato.

    Usa Python 3.11, 3.12 o 3.13 e instala uv. Luego ejecuta estos comandos:

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

    La comparación crea un entorno .venv independiente para cada etapa e instala las dependencias con uv sync --frozen. Todas las etapas usan el intérprete de Python con el que se inició la comparación y los mismos fixtures de HTTP y de esquema. Las versiones de las dependencias se mantienen fijas.

    Se excluyen Python 3.14 y las versiones posteriores porque el puente de compatibilidad con Pydantic v1 no las admite. Si la ejecución se completa correctamente, termina con este mensaje:

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

    Revisa evidence/legacy.json, prepared.json, target.json y summary.json. Contienen las respuestas observadas, las versiones del entorno de ejecución, los hashes del código fuente, de los fixtures y de los archivos de bloqueo, los comandos, los códigos de salida y las rutas de los registros sin procesar.

    El ejecutor prueba los puntos de acceso HTTP de la aplicación de ejemplo con el cliente de pruebas de FastAPI, TestClient, que funciona en el mismo proceso. Ejecútalo dentro de la tarea en la nube que hayas elegido y conserva la evidencia de esa tarea.

    Ejecuta python3 run.py sin --compare-stages solo para comprobar el modelo de destino en un entorno que tenga instalado Pydantic v2. Ese modo independiente comprueba siete casos de validación y serialización. No realiza comprobaciones HTTP ni compara los resultados entre etapas. Para verificar una migración, usa el comando de comparación completa indicado arriba.

    La comparación completa comprueba 23 casos HTTP en cada etapa, el documento público de OpenAPI y la serialización de respuestas. Todas las comprobaciones pasaron en ejecuciones locales con Python 3.11, 3.12 y 3.13. El archivo README.md incluido en la descarga contiene las versiones probadas y el historial de ejecución. Ejecuta la comparación actual en tu entorno en la nube para registrar allí los resultados.

    Mantén separadas las dependencias de cada etapa

    Etapa Python / FastAPI / Pydantic Alcance de la migración
    Original 3.11–3.13 / 0.119.0 / 1.10.22 Los modelos existentes importan desde pydantic.
    Base preparada 3.11–3.13 / 0.119.0 / 2.12.3 Ambos modelos usan pydantic.v1. Registra la versión exacta de v1 incluida.
    Destino 3.11–3.13 / 0.119.0 / 2.12.3 Solo OrderInput pasa a v2. La ruta adyacente conserva su propio modelo v1.

    Los archivos de bloqueo de dependencias de la etapa preparada y la de destino son idénticos. La migración del módulo cambia el modelo y su método directo de serialización, sin modificar el entorno de dependencias.

    La preparación se revisa como un cambio independiente. Este paso también hace que los adaptadores de las rutas que siguen en v1 generen diccionarios con los alias como claves mediante dict(by_alias=True) antes de validar la respuesta. Si se devuelve directamente el modelo v1 en el entorno v2, se pueden perder los alias públicos.

    El adaptador de pedidos de la etapa de destino usa model_dump(by_alias=True), y la ruta v1 adyacente conserva el adaptador de la etapa de preparación.

    Según la guía de migración de FastAPI, la compatibilidad temporal que permite usar rutas v1 y rutas v2 en una misma aplicación solo está disponible desde la versión 0.119.0 hasta la 0.127.x. Se eliminó en la versión 0.128.0.

    Este ejemplo fija la versión 0.119.0 para demostrar esa transición. En una aplicación con una versión más reciente de FastAPI, delimita la migración de modo que todo el código dentro de ese alcance use v2. Para el puente temporal, designa a una persona responsable y define en qué condiciones se retirará. No anides modelos v1 y v2. Este puente no permite migrar a Python 3.14.

    Revisa las pruebas de contrato

    Los casos HTTP con valores fijos cubren solicitudes válidas, notas omitidas o con valor nulo explícito, identificadores ausentes o no válidos, varios campos desconocidos, combinaciones de errores de campo, notas no válidas, JSON mal formado, tipos de contenido incorrectos, solicitudes sin cuerpo, alias, cuerpos que contienen arreglos o cadenas y la ruta heredada adyacente. Comparan los códigos de estado, el encabezado Content-Type y las respuestas JSON completas una vez analizadas, sin quitar campos de error ni cambiar los tipos esperados.

    Desde la etapa original, la aplicación define tanto la estructura de las respuestas de error del servicio como su documento público de OpenAPI. Los adaptadores de errores y esquemas se mantienen. La etapa de destino conserva el orden original de los errores: primero los de los campos declarados y luego los de las claves desconocidas, en orden alfabético.

    Las pruebas comparan el arreglo error.fields completo en el orden en que se recibe, sin reordenar las respuestas observadas. El ejecutor registra por separado el esquema nativo de cada modelo y verifica que los esquemas nativos difieran. Que el esquema público coincida no significa que los esquemas generados por la dependencia sean idénticos.

    El servicio conserva el comportamiento original al procesar entradas: acepta como objeto un arreglo de pares como [["order_id", "ord_123"]]. Tanto [] como una cadena JSON vacía se convierten en objetos vacíos y generan un error por falta de identificador. Una cadena no vacía sigue siendo un cuerpo no válido. El modelo de destino conserva las conversiones que se completan correctamente con dict(value). Para aceptar únicamente objetos, la persona responsable debe decidir si cambia el contrato. No basta con modificar el resultado esperado de una prueba.

    En tu repositorio, registra y fija las respuestas reales que produce la aplicación antes de editar el código. Conserva los campos opcionales con valores predeterminados explícitos, el nivel de rigor de la validación y el tratamiento de los campos desconocidos. Prueba el manejador de errores real y la serialización de respuestas.

    Los errores de validación sin procesar varían según la versión. En Pydantic: guía de migración también se describen cambios en los alias, la conversión implícita de tipos, los campos opcionales y las funciones de validación personalizadas. La versión v2 no convierte las excepciones TypeError que se lanzan dentro de esas funciones en ValidationError.

    Examina el ejecutor de pruebas de contrato

    Este fragmento proviene del archivo run.py incluido en la descarga. Extrae el ZIP y ejecuta los archivos completos. El ZIP incluye las etapas con sus versiones de dependencias fijas y los fixtures HTTP.

    Compara las tres etapas
    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."
        )

    Ejecuta el flujo de trabajo en tu repositorio

    1. Redacta las especificaciones. Identifica el módulo, el código que lo invoca directamente, la persona responsable, las versiones aprobadas, las pruebas de contrato, el código que queda fuera del alcance y los pasos para revertir el cambio. Detente si migrar esa parte requiere una actualización más amplia de las dependencias o cambia el comportamiento público.
    2. Establece el punto de partida. Pídele a Codex que revise AGENTS.md, las especificaciones, la guía oficial de migración, los archivos de bloqueo, el código que invoca el módulo y las pruebas. Registra el commit inicial y las versiones instaladas. Si alguna comprobación inicial falla, explica o corrige la falla antes de afirmar que el comportamiento coincide.
    3. Prepara la compatibilidad por separado. Si las dependencias compartidas vinculan el módulo con otras partes del código, revisa primero ese cambio de preparación. Demuestra que las pruebas con los mismos fixtures pasan tanto en la versión original como en la base preparada.
    4. Migra una sola parte. Usa el prompt inicial de esta página. Modifica el módulo y su adaptador directo, conserva los módulos que no estén relacionados e identifica el código de compatibilidad que siga en uso.
    5. Ejecuta las mismas pruebas. Compara los fixtures de HTTP y de esquema de las etapas inicial y final sin cambiar los resultados esperados. Ejecuta las pruebas de integración y las comprobaciones de lint y de tipos que correspondan al cambio. Guarda la salida junto con una referencia al estado final del código fuente y de las dependencias.
    6. Revisa la entrega. Examina el diff, la evidencia recopilada, las diferencias pendientes, el puente de compatibilidad que siga en uso y los pasos para revertir el cambio. Crea un Pull Request en borrador solo si las herramientas y los permisos del repositorio lo permiten. Deja la fusión, el despliegue y la migración de la siguiente parte en manos de quien revise el cambio.

    Registra lo que verificaste

    Usa un registro de evidencia para cada criterio:

    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]

    Que la importación se complete correctamente, que pase una comprobación de lint o que se regenere una instantánea no demuestra que la API conserve su comportamiento. Registra por separado las diferencias intencionales y vuelve a ejecutar las comprobaciones si cambia el código fuente final. Al adaptar el ejemplo, incluye en las pruebas de contrato el código que invoca tu aplicación y los servicios de la aplicación.

    Related use cases