Skip to content

fix(api): fix SimulatedProbeResult ser/deser #18177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 25, 2025

Conversation

sfoster1
Copy link
Member

@sfoster1 sfoster1 commented Apr 24, 2025

We weren't deserializing the simulation standin properly, which broke runlog downloading. Now we are.

Also, we shouldn't be dumping it to the runlog anymore. That was happening because of pydantic/pydantic#6830 which causes mis-serialization of fields typed as Model | NativeType, where NativeType is some scalar python type (i.e. float, int) and Model is some pydantic model. With the model first, serializing the container will always result in the serialization of the default construction of the model, even if the attribute actually contained the native type. Flipping the order makes it work correctly, for some reason. Luckily this is consistent enough to be testable.

Closes RQA-4147
Closes EXEC-1429
Apparently,
Closes EXEC-1358

We weren't deserializing the simulation standin properly, which broke
runlog downloading. Now we are. To do: why is this ever in a runlog.

Closes RQA-4147
@sfoster1 sfoster1 requested a review from a team as a code owner April 24, 2025 21:49
Copy link
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Just minor suggestions.

I think this closes EXEC-1358, yeah? Meaning we can delete this workaround:

# TODO(cm): 3/21/25: refactor SimulatedLiquidProbe in a way that
# doesn't require models like this one that are just using it to
# need a custom validator
@field_validator("probed_height", "probed_volume", mode="before")
@classmethod
def validate_simulated_probe_result(
cls, input_val: object
) -> LiquidTrackingType | None:
"""Return the appropriate input to WellInfoSummary from json data."""
if input_val is None:
return None
if isinstance(input_val, LiquidTrackingType):
return input_val
if isinstance(input_val, str) and input_val == "SimulatedProbeResult":
return SimulatedProbeResult()
raise ValueError(f"Invalid input value {input_val} to WellInfoSummary")

@@ -17,6 +18,14 @@ def serialize_model(self) -> str:
"""Serialize instances of this class as a string."""
return "SimulatedProbeResult"

@model_validator(mode="before")
@classmethod
def model_validator(cls, data: Any) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can we use object instead of Any here? It doesn't matter so much for the return type, but it's meaningfully safer for the data arg.

The examples in the Pydantic docs recommend Any but I think that's a bad recommendation.

Suggested change
def model_validator(cls, data: Any) -> Any:
def validate_model(cls, data: object) -> object:

(Also renaming to validate_model for symmetry with the existing serialize_model)

Comment on lines 24 to 37


class TestModel(BaseModel):
"""Test model for deserializing SimulatedProbeResults."""

value: float | SimulatedProbeResult


def test_deserializes_simulated_liquid_probe() -> None:
"""Should be able to roundtrip our simulated results."""
base = TestModel(value=SimulatedProbeResult())
serialized = base.model_dump_json()
deserialized = TestModel.model_validate_json(serialized)
assert isinstance(deserialized.value, SimulatedProbeResult)
Copy link
Contributor

@SyntaxColoring SyntaxColoring Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To sate my paranoia, can we expand this test to cover the case where the JSON has a value that doesn't represent a SimulatedProbeResult?

Something like:

Suggested change
class TestModel(BaseModel):
"""Test model for deserializing SimulatedProbeResults."""
value: float | SimulatedProbeResult
def test_deserializes_simulated_liquid_probe() -> None:
"""Should be able to roundtrip our simulated results."""
base = TestModel(value=SimulatedProbeResult())
serialized = base.model_dump_json()
deserialized = TestModel.model_validate_json(serialized)
assert isinstance(deserialized.value, SimulatedProbeResult)
def test_simulated_probe_result_serdes() -> None:
"""Should be able to roundtrip our simulated results."""
class TestModel(BaseModel):
"""Test model for deserializing SimulatedProbeResults."""
value: float | SimulatedProbeResult
# Should be able to roundtrip our simulated results:
base = TestModel(value=SimulatedProbeResult())
serialized = base.model_dump_json()
deserialized = TestModel.model_validate_json(serialized)
assert isinstance(deserialized.value, SimulatedProbeResult)
# Should allow parsing to fall back to other union elements (in this case a float):
assert TestModel.model_validate_json('{"value": 1.23}') == TestModel(value=1.23)
# Parsing should totally error out if the value doesn't fit into any of the union elements:
with pytest.raises(ValidationError):
TestModel.model_validate_json(
'{"value": "not a valid float or SimulatedProbeResult"}'
)

pydantic/pydantic#6830 means that pydantic
will not always properly serialize a field declared as Model |
NativeType, where Model is some pydantic model and NativeType is some
python scalar (i.e. float, str). It will always serialize the default
construction of the model instead.

If you make the union be NativeType | Model it works fine.

Closes EXEC-1429
@sfoster1 sfoster1 changed the title fix(api): fix SimulatedProbeResult deser fix(api): fix SimulatedProbeResult ser/deser Apr 25, 2025
@sfoster1 sfoster1 merged commit 1f15b1c into chore_release-8.4.0 Apr 25, 2025
24 checks passed
@sfoster1 sfoster1 deleted the RQA-4147-runlog-simulated-probe-results branch April 25, 2025 15:36
Copy link
Contributor

@caila-marashaj caila-marashaj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird that it works like this but nice !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants