Codex use case
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.
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
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.
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.
Related links
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
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
-
Elige Trabajar en > Nube y selecciona un entorno publicado.
-
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.
-
Revisa el informe de configuración, corrige los problemas detectados en las comprobaciones y selecciona Publicar antes de iniciar una tarea nueva.
- Sigue la guía de entornos en la nube.
- Guarda las instrucciones del repositorio en AGENTS.md.
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
- 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.
- 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. - 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.
- 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.
- 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.
- 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
Investigar un incidente y probar una corrección
Dale a Codex Cloud la documentación del incidente sin datos sensibles y un entorno de...
Convierte un issue en un pull request con cambios probados
Dale a Codex un issue de alcance definido, un entorno en la nube ya preparado y una lista...
Crea juegos de navegador
Usa Codex para convertir la descripción de tu juego en un plan bien definido y luego...