Skip to content

refactor(api): Move more vector arithmetic from Pydantic models to Point #18699

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 6 commits into from
Jun 23, 2025

Conversation

SyntaxColoring
Copy link
Contributor

@SyntaxColoring SyntaxColoring commented Jun 20, 2025

Changelog

  • Delete arithmetic methods like __add__() from Pydantic models like LabwareOffsetVector. Now, callers should convert the model to a Point and use Point's arithmetic methods.
  • To make it easier to convert to Point, instead of defining to_point() on every vector-like Pydantic model, add a Point.from_xyz_attrs(model) method that works on anything with x/y/z attributes. Delete the existing to_point() methods.

The intent is to encourage us to consistently use Points for everything mathematical, and reserve Pydantic models for just the interface boundaries where we're converting to/from JSON. I think we want to do this because:

  • We have a lot of vector-like Pydantic models (LabwareOffsetVector, Vec3f, Vector3D, etc.). We are working to consolidate these, but I doubt that we'll ever have one Pydantic vector model to rule them all. In part, because parsing/serialization contexts have different needs. Sometimes we want the fields to have defaults, sometimes we don't; sometimes we care about float vs. int, sometimes we don't.
  • Some of these Pydantic models, in particular in protocol_engine/types/module.py, really ought to live in shared-data. We can't move them to shared-data if they have a dependency on opentrons.types.Point.

Test Plan and Hands on Testing

Just existing automated tests.

Review requests

Ideologically, does this seem good?

Risk assessment

Mostly low, but there is one hazard.

Point's operators are defined like:

    def __add__(self, other: Any) -> Point:
        if not isinstance(other, Point):
            return NotImplemented
        return Point(self.x + other.x, self.y + other.y, self.z + other.z)

An unfortunate side effect of other: Any and return NotImplemented is that if you accidentally do point + not_a_point, you won't get a type-checking error, and you'll probably get a TypeError at runtime, unless the other side defined a suitable __radd__() (which it probably didn't). So we need to be careful that in my changes, I'm never doing point + not_a_point.

I mitigated this by temporarily changing the type annotation locally and looking to see if that surfaced any new errors. I don't think we can actually merge that change because Point.__add__ needs to remain compatible with NamedTuple.__add__. Perhaps this indicates we shouldn't be subclassing from NamedTuple.

The `return NotImplemented` statement here didn't seem right. `NotImplemented` is a magic thing that Python uses in operators like `__add__()` and `__sub__()`, and it doesn't have that magic when it's used in normal methods like `magnitude_to()`.
@SyntaxColoring SyntaxColoring requested a review from mjhuff June 20, 2025 21:13
@SyntaxColoring SyntaxColoring requested a review from a team as a code owner June 20, 2025 21:13
def magnitude_to(self, other: Any) -> float:
if not isinstance(other, Point):
return NotImplemented
def magnitude_to(self, other: Point) -> float:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

One unrelated change here: This use of NotImplemented did not seem right. NotImplemented is a magic sentinel for use in things like __add__(). It isn't meant for, and has no special meaning in, normal methods like magnitude_to().

Copy link
Contributor

@mjhuff mjhuff left a comment

Choose a reason for hiding this comment

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

Like this a lot, thank you. Agreed that we'll probably never get to the point of using just one XYZ type, but this change should steer us to a universal XYZ adapter type, which is the next best thing.

Copy link
Member

@sfoster1 sfoster1 left a comment

Choose a reason for hiding this comment

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

Agree with the reasoning. can we refactor that last thing in move labware to get rid of the step back there

This avoids an especially ugly conversion in MoveLabwareImplementation.
@SyntaxColoring SyntaxColoring merged commit e161ab1 into edge Jun 23, 2025
21 of 24 checks passed
@SyntaxColoring SyntaxColoring deleted the arithmetic_only_through_point branch June 23, 2025 22:03
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