Apariencia
❌ ValueError: Cannot parse condition starting at: + :delta >= :zero
Clase 8 ·
src/inventory/app.py(y su copia enconsole_lambda/lambda_function.py) ·update_inventory()
🧨 Qué pasó
python
result = table.update_item(
Key={"product_id": product_id},
UpdateExpression="SET available_stock = available_stock + :delta",
ConditionExpression=(
"attribute_exists(product_id) AND "
"available_stock + :delta >= :zero"
),
ExpressionAttributeValues={
":delta": Decimal(delta_stock),
":zero": Decimal(0),
},
ReturnValues="ALL_NEW",
)Contra una tabla DynamoDB real (verificado con moto, que emula el servicio real de AWS byte a byte en su parser de expresiones):
ValueError: Cannot parse condition starting at: + :delta >= :zero🔍 Causa
DynamoDB tiene dos lenguajes de expresión distintos, con reglas distintas:
| Expresión | Para qué sirve | ¿Admite aritmética (+, -)? |
|---|---|---|
UpdateExpression | Describir qué cambiar en un item (SET, ADD, REMOVE) | ✅ Sí — SET x = x + :delta es válido |
ConditionExpression | Describir una comparación que debe cumplirse antes de aplicar la operación | ❌ No — solo compara un path o un value contra otro |
available_stock + :delta no es ni un nombre de atributo (path) ni un valor (value) — es una expresión aritmética, y la gramática de ConditionExpression simplemente no la reconoce como operando válido. El parser lee available_stock como un path correcto, y se rompe apenas encuentra el + después.
✅ Solución
Calcular el umbral necesario en Python, antes de armar la condición, y comparar el atributo directo contra ese valor ya calculado — toda la aritmética queda dentro del UpdateExpression (donde sí es válida), nunca dentro del ConditionExpression:
python
result = table.update_item(
Key={"product_id": product_id},
UpdateExpression="SET available_stock = available_stock + :delta", # la suma va acá
ConditionExpression=(
"attribute_exists(product_id) AND "
"available_stock >= :min_needed" # acá solo comparación directa
),
ExpressionAttributeValues={
":delta": Decimal(delta_stock),
# si delta es negativo, hace falta AL MENOS esa cantidad disponible;
# si delta es positivo (sumar stock), la condición es siempre verdadera
":min_needed": Decimal(-delta_stock) if delta_stock < 0 else Decimal(0),
},
ReturnValues="ALL_NEW",
)Verificado con moto simulando la tabla real: delta_stock=-4 sobre available_stock=20 actualiza a 16 (200 OK); delta_stock=-999 sobre el mismo item lanza ConditionalCheckFailedException (409, como se espera); delta_stock=5 (sumar stock) siempre pasa.
💡 Por qué
reserve_inventory(la otra operación condicional del mismo archivo) nunca tuvo este bug: su condición ya era"attribute_exists(product_id) AND available_stock >= :q"— comparaba el atributo directo contra un valor, sin sumarle nada adentro de la expresión. El problema es específico de mezclar una resta/suma DENTRO delConditionExpression.
⚠️ Este mismo patrón está duplicado en dos archivos del proyecto de la sesión (
src/inventory/app.pyyconsole_lambda/lambda_function.py) — ver Clase 8 → sección 8 sobre por qué mantener dos copias del mismo código facilita que un bug corregido en un lado quede sin corregir en el otro.
✅ Confirmado en AWS real (no solo simulado)
El fix se aplicó al src/inventory/app.py real del repo de la sesión, y Styp lo desplegó con sam build && sam deploy en su propia cuenta AWS (us-east-2). La cadena completa contra la URL pública real del API Gateway dio exactamente lo esperado:
bash
curl -X POST .../inventory -d '{"product_id":"PROD-001","product_name":"Laptop empresarial","available_stock":20}'
# → 201, available_stock: 20, reserved_stock: 0
curl -X PATCH .../inventory/PROD-001 -d '{"delta_stock": -4}'
# → 200, available_stock: 16 ← antes del fix, esto daba el error de sintaxis
curl -X PATCH .../inventory/PROD-001/reserve -d '{"quantity": 3}'
# → 200, available_stock: 13, reserved_stock: 3console_lambda/lambda_function.py (la copia de consola, sección 8) sigue con el bug sin corregir a propósito — sigue siendo el Ejercicio 19 de la Clase 8 (aplicar el mismo fix ahí, como práctica).