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

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

Draft
wants to merge 56 commits into
base: edge
Choose a base branch
from

Conversation

ahiuchingau
Copy link
Contributor

@ahiuchingau ahiuchingau commented Apr 11, 2024

Overview

Let's try this again

Closes PLAT-326.

Test Plan

Changelog

Update our robot software (and everything that depends on it) from Pydantic v1 to Pydantic v2:

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

buildroot changes in [todo]

Review requests

Risk assessment

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:

Comment on lines +12 to +16
DatetimeType = typing.Annotated[
datetime,
PlainSerializer(lambda x: x.isoformat(), when_used="json"),
]

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm inferring that this is replacing the old json_encoders config. Does something need to replace the old json_decoders config, to match?

Comment on lines +15 to +18
DatetimeType = typing.Annotated[
datetime,
PlainSerializer(lambda x: x.isoformat(), when_used="json"),
]
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

api/src/opentrons/protocol_engine/clients/sync_client.py Outdated Show resolved Hide resolved
# Each time a TypeAdapter is instantiated, it will construct a new validator and
# serializer. To improve performance, TypeAdapters are instantiated once.
# See https://docs.pydantic.dev/latest/concepts/performance/#typeadapter-instantiated-once
CommandCreateAdatper: TypeAdapter[CommandCreate] = TypeAdapter(CommandCreate) # type: ignore[arg-type]
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. Typo: Adatper instead of Adapter
  2. What is this TypeAdapter doing?

@@ -230,7 +230,7 @@ def handle_action(self, action: Action) -> None: # noqa: C901
# request > command mapping, figure out how to type precisely
# (or wait for a future mypy version that can figure it out).
# For now, unit tests cover mapping every request type
queued_command = action.request._CommandCls.construct(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be .model_construct, for performance reasons?

Comment on lines +19 to +24
class Vec3f(BaseModel, Generic[NumberType]):
"""A 3D Vector."""

x: NumberType
y: NumberType
z: NumberType
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. The name Vec3f comes from "vector of 3 floats." If this is generic across other kinds of NumberType, let's rename it to just Vec3.
  2. Since we're really pushing this deep down into common shared code, I'm worried that someone will come along later and add an = 0 default for something without realizing that it will affect a million things. Let's spell out that this is specifically for vectors where all elements are required, to hopefully prompt people to use a different type if that's not the behavior that they want.
Suggested change
class Vec3f(BaseModel, Generic[NumberType]):
"""A 3D Vector."""
x: NumberType
y: NumberType
z: NumberType
class Vec3f(BaseModel, Generic[NumberType]):
"""A 3D vector where all elements are required."""
x: NumberType
y: NumberType
z: NumberType

assert loaded_model.channels == types.PipetteChannelType.NINETY_SIX_CHANNEL

model_dict = loaded_model.model_dump()
# each field should be the value of the enum class
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice.

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!

Copy link
Contributor

Choose a reason for hiding this comment

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

Same as the other comment—for performance reasons, can we change the .construct()s to .model_construct()s instead of changing them to fully-validating __init__() calls?

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

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

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 ==.

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
For some reason, this is necessary for `pipenv lock --dev` to succeed. Otherwise, when it resolves performance-metrics' subdependencies, it mistakenly tries to get opentrons-shared-data from an external source instead of from this local path, and then complains because the external opentrons-shared-data's dependencies are incompatible with api's dependencies.
Pydantic doesn't allow this anymore.
Just for consistency with the existing pattern.
Handy dandy scripts:

Find projects whose dependencies have changed:
    git diff --name-only edge | grep -E 'setup\.py|Pipfile$'

Iterate over projects and re-lock them:
    for dir in shared-data/python server-utils hardware api system-server robot-server g-code-testing; do echo $dir && pushd $dir && pipenv lock --dev && popd; done
This has merge conflicts, so let's make sure we don't flagrantly break it.
@SyntaxColoring SyntaxColoring deleted the chore_update-pydantic-v2 branch May 28, 2024 17:14
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.

None yet

3 participants