Skip to content
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

refactor: Migrate Python projects from Pydantic v1 to v2 #14871

Open
wants to merge 140 commits into
base: edge
Choose a base branch
from

Conversation

ahiuchingau
Copy link
Contributor

@ahiuchingau ahiuchingau commented Apr 11, 2024

[PR originally by @ahiuchingau, current description by @SyntaxColoring]

Overview

This updates our robot-stack Python projects from Pydantic v1 to v2.

Closes PLAT-326 and GitHub issue #13983.

Affected projects that run on the robot:

  • api
  • robot-server
  • shared-data
  • hardware
  • server-utils
  • system-server
  • performance-metrics (re-lock only, depends on shared-data)

Affected projects that only run in CI and on our laptops:

  • g-code-testing
  • hardware-testing
  • abr-testing

Test Plan

When the https://github.com/Opentrons/buildroot and https://github.com/Opentrons/oe-core changes are ready:

  • Make sure all servers still boot on an OT-2 without errors
  • Make sure all servers still boot on a Flex without errors
  • Try running a protocol or something and make sure the server doesn't return an error or take an obscenely long time to respond.

Beyond that, this PR touches a million little things in a million little ways, so it's difficult to test. We should try to merge it early in the release cycle to give us time to shake things out.

Changelog

See https://docs.pydantic.dev/latest/migration/#migration-guide for everything that has changed from Pydantic v1 to v2.

The basic methodology of this PR is:

  • Update setup.py, Pipfile, and Pipfile.lock files to a new Pydantic version, trying to follow Unfortunately, FastAPI is tightly coupled to Pydantic, so we need to update it too. The FastAPI bump is kept minimal.
  • Run bump-pydantic on all projects. This automatically does a lot of the grunt work, but it does need manual follow-up.
  • Manually fix up lots of little things
  • Do global find+replaces for some trivial renames. I moved some of this to a separate PR, #17123, because the GitHub web UI was struggling with the big diff.

Review requests

  • Do the setup.py, Pipfile, and Pipfile.lock files look good? We're trying to align on a definition of "good" here.
  • Do all Pydantic migrations look correct? Reference: https://docs.pydantic.dev/latest/migration/#migration-guide
    • Do the rewritten validators look correct? Some of these needed manual intervention.
    • Do the = None additions look correct? bump-pydantic added these automatically to match prior parse behavior—see https://docs.pydantic.dev/latest/migration/#required-optional-and-nullable-fields. As far as I can tell, these additions are always safe. But defaulting to None may not be what we actually want, e.g. it may not match the underlying JSON schema.
  • This was a long-lived PR that changed hands several times, so there are definitely vestigial things left over from earlier attempts. If something looks unexplained or out of place to you, please speak up.

Risk assessment

Performance

This will, at least in the short term, make robot-server take much longer to start up, and make the tests much slower. This is a known Pydantic v2 problem (pydantic/pydantic#6768 etc.). Earlier testing on a Flex found it slowed from 46s to 1m54s (2.5x). I don't think we'll be able to get it back down to Pydantic v1 times, but some proofs of concept suggest that we can mitigate it to only ~1.6x slower. There are some ideas in EXEC-1060.

Correctness

High-risk due to the breadth of changes: storage reads and writes, HTTP requests and responses, communication between the opentrons.protocol_api and opentrons.protocol_engine, ...

  • Pydantic's type coercion behavior has changed, trending in the direction of doing less type coercion. This is a good direction, but it is basically impossible to audit affected one Python protocol in the snapshot tests—see the inline review comments below.

@ahiuchingau ahiuchingau force-pushed the chore_update-pydantic-v2 branch from e83d1b5 to 80a7000 Compare April 11, 2024 17:03
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.

I'm about 60% through the changes so far. Mostly looks great, only a few comments and questions:

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.

I'm caught up!

api/src/opentrons/protocol_runner/legacy_command_mapper.py Outdated Show resolved Hide resolved
robot-server/robot_server/log.txt Outdated Show resolved Hide resolved
Comment on lines 59 to 62
"fastapi==0.99.1",
"fastapi>=0.100.0",
"python-dotenv==1.0.1",
"python-multipart==0.0.6",
"pydantic==1.10.12",
"pydantic>=2.0.0,<3",
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these need to be pinned to a specific version like they were before?

Copy link

@ecederstrand ecederstrand May 24, 2024

Choose a reason for hiding this comment

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

Please don't pin to exact versions unless strictly necessary. See e.g. #11905

Copy link
Contributor

@SyntaxColoring SyntaxColoring May 24, 2024

Choose a reason for hiding this comment

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

This is robot-server, which is not published on PyPI, so it should not affect what gets pulled in when you do pip install opentrons.

I'm a bit shaky on our packaging mechanisms, but I think this specific setup.py may not even affect what we install on robots. I'm sure we can make this less confusing somehow. But for the sake of not changing too many things in this PR, I think we'll keep the ==.

[Edit: Opened a discussion here for Opentrons people and added this to the review requests.]

ahiuchingau and others added 2 commits April 19, 2024 13:09
Conflicts:
* api/Pipfile.lock (took edge, will need to re-lock)
* api/src/opentrons/calibration_storage/deck_configuration.py
* api/src/opentrons/cli/analyze.py
* api/src/opentrons/protocol_api/core/engine/protocol.py
* api/src/opentrons/protocol_engine/commands/calibration/calibrate_gripper.py
* api/src/opentrons/protocol_engine/commands/calibration/calibrate_pipette.py
* api/src/opentrons/protocol_engine/commands/command.py
* api/src/opentrons/protocol_engine/commands/custom.py
* api/src/opentrons/protocol_engine/types.py
* api/src/opentrons/protocol_runner/legacy_command_mapper.py
* api/tests/opentrons/cli/test_cli.py
* api/tests/opentrons/protocol_engine/state/command_fixtures.py
* api/tests/opentrons/protocol_engine/state/test_addressable_area_store.py
* api/tests/opentrons/protocol_engine/state/test_geometry_view.py
* api/tests/opentrons/protocol_runner/test_protocol_runner.py
* robot-server/Pipfile
* robot-server/Pipfile.lock (took edge, will need to re-lock)
* robot-server/robot_server/deck_configuration/defaults.py
* robot-server/robot_server/persistence/pydantic.py
* shared-data/command/schemas/8.json (took edge, will need to regenerate)
* shared-data/python/tests/deck/test_typechecks.py
Copy link
Contributor

@SyntaxColoring SyntaxColoring Dec 14, 2024

Choose a reason for hiding this comment

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

All of the diffs in analyses-snapshot-testing/ are the same. Some floats used to get serialized like 0 and now they're getting serialized like 0.0. I've already looked through these files, so I don't think you have to.

There is one exception: https://github.com/Opentrons/opentrons/pull/14871/files#r1884999286

@Opentrons Opentrons deleted a comment from github-actions bot Dec 16, 2024
This was previously deleted before this PR, in commit 50d3208, but it snuck back in. Maybe during merge conflict resolution or something.
This appears unnecessary now. When this comment was written, this PR was on Pydantic 2.6, and now we're on 2.9. Maybe that fixed it?
I think I was talking about wanting to move the list loop into pydantic-core, by parsing this through a TypeAdapter(list[Command]) instead of a TypeAdapter(Command). That might be worth trying, but not in this PR.
SyntaxColoring added a commit that referenced this pull request Dec 17, 2024
This reverts commit 0d7f92b from PR #14871.
Integrate with the new mmFromEdge param in TouchTipParams.
@SyntaxColoring SyntaxColoring marked this pull request as ready for review December 17, 2024 00:49
@SyntaxColoring SyntaxColoring requested review from a team as code owners December 17, 2024 00:49
Comment on lines 44 to +56
def dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
"""Always exclude `None` when serializing to an object.

The OpenAPI spec marks `Optional` BaseModel fields as omittable, but
not nullable. This `dict` method override ensures that `null` is never
returned in a response, which would violate the spec.
With Pydantic v1, the OpenAPI spec described `Optional`(i.e., possibly
`None`-valued) fields as omittable, but not nullable. This did not match
Pydantic's actual serialization behavior, which serialized Python `None` to
JSON `null` by default. This method override fixed the mismatch by making
Pydantic omit the field from serialization instead.

With Pydantic v2, the OpenAPI spec does describe `Optional` fields as nullable,
matching Pydantic's serialization behavior. We therefore no longer need this
override to make them match. However, removing this override and changing
serialization behavior at this point would risk breaking things on the client.
Copy link
Contributor

Choose a reason for hiding this comment

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

Heads up for this.

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.

5 participants