fix(SKY-8799): preserve ISO datetime strings in workflow YAML loading (#5421)

This commit is contained in:
Aaron Perez 2026-04-07 21:27:48 -05:00 committed by GitHub
parent 793fb7ea01
commit 6bab0aeaee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 117 additions and 6 deletions

View file

@ -0,0 +1,77 @@
"""Regression tests for SKY-8799.
PyYAML's default ``SafeLoader`` resolves ISO 8601 strings into Python
``datetime`` objects. When users embed such strings inside the
``default_value`` of a JSON workflow parameter, those values become
``datetime`` instances on the way in and then crash ``json.dumps`` on
the way back out, producing::
TypeError: Object of type datetime is not JSON serializable
``skyvern.utils.yaml_loader.safe_load_no_dates`` removes the timestamp
implicit resolver so the values stay as plain strings.
"""
import json
import yaml
from skyvern.utils.yaml_loader import safe_load_no_dates
ISO_BLOB = """
parameters:
- parameter_type: workflow
key: payload
workflow_parameter_type: json
default_value:
id: "12345"
metadata:
# Unquoted ISO 8601 strings are what trip the default SafeLoader.
created_at: 2023-10-27T10:00:00Z
updated_at: 2023-10-28T14:30:00Z
tags: ["primary", "test-data"]
"""
def test_default_safe_load_does_parse_datetimes() -> None:
# Sanity check: documents the behavior we are working around.
parsed = yaml.safe_load(ISO_BLOB)
default_value = parsed["parameters"][0]["default_value"]
# The default loader turns the unquoted-looking timestamps into datetimes,
# which is exactly what breaks downstream JSON serialization.
assert not isinstance(default_value["metadata"]["created_at"], str)
def test_safe_load_no_dates_keeps_iso_strings_as_strings() -> None:
parsed = safe_load_no_dates(ISO_BLOB)
metadata = parsed["parameters"][0]["default_value"]["metadata"]
assert metadata["created_at"] == "2023-10-27T10:00:00Z"
assert metadata["updated_at"] == "2023-10-28T14:30:00Z"
assert isinstance(metadata["created_at"], str)
assert isinstance(metadata["updated_at"], str)
def test_safe_load_no_dates_round_trips_through_json() -> None:
parsed = safe_load_no_dates(ISO_BLOB)
# The whole point: the parsed structure must be JSON-serializable
# without a custom encoder.
serialized = json.dumps(parsed)
assert "2023-10-27T10:00:00Z" in serialized
def test_safe_load_no_dates_preserves_other_implicit_types() -> None:
parsed = safe_load_no_dates(
"""
an_int: 42
a_float: 3.14
a_bool: true
a_null: null
a_list: [1, 2, 3]
"""
)
assert parsed["an_int"] == 42
assert parsed["a_float"] == 3.14
assert parsed["a_bool"] is True
assert parsed["a_null"] is None
assert parsed["a_list"] == [1, 2, 3]