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

⬆️ Upgrade models-library (pydantic v2) #6333

Conversation

giancarloromeo
Copy link
Contributor

@giancarloromeo giancarloromeo commented Sep 9, 2024

What do these changes do?

Related issue/s

How to test

Dev-ops checklist

Copy link

codecov bot commented Sep 9, 2024

Codecov Report

Attention: Patch coverage is 90.64857% with 62 lines in your changes missing coverage. Please review.

Please upload report for BASE (pydantic_v2_migration@6d6f5c5). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...ary/src/models_library/utils/json_serialization.py 65.6% 6 Missing and 5 partials ⚠️
...models_library/utils/_original_fastapi_encoders.py 57.8% 5 Missing and 3 partials ⚠️
.../models_library/api_schemas_directorv2/clusters.py 66.6% 5 Missing ⚠️
...odels-library/src/models_library/projects_nodes.py 68.7% 5 Missing ⚠️
...src/models_library/api_schemas_webserver/groups.py 66.6% 4 Missing ⚠️
...els-library/src/models_library/user_preferences.py 60.0% 4 Missing ⚠️
...-library/src/models_library/api_schemas_storage.py 81.2% 3 Missing ⚠️
...ckages/models-library/src/models_library/docker.py 70.0% 3 Missing ⚠️
...ages/models-library/src/models_library/payments.py 70.0% 3 Missing ⚠️
...rary/src/models_library/service_settings_labels.py 91.6% 2 Missing and 1 partial ⚠️
... and 10 more
Additional details and impacted files

Impacted file tree graph

@@                   Coverage Diff                   @@
##             pydantic_v2_migration   #6333   +/-   ##
=======================================================
  Coverage                         ?   82.3%           
=======================================================
  Files                            ?     297           
  Lines                            ?   10035           
  Branches                         ?     704           
=======================================================
  Hits                             ?    8261           
  Misses                           ?    1684           
  Partials                         ?      90           
Flag Coverage Δ
unittests 82.3% <90.6%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...models-library/src/models_library/access_rights.py 100.0% <100.0%> (ø)
...models-library/src/models_library/aiodocker_api.py 70.8% <100.0%> (ø)
...ary/src/models_library/api_schemas__common/meta.py 100.0% <100.0%> (ø)
.../models_library/api_schemas_api_server/api_keys.py 100.0% <100.0%> (ø)
...src/models_library/api_schemas_catalog/services.py 100.0% <100.0%> (ø)
...dels_library/api_schemas_catalog/services_ports.py 95.6% <100.0%> (ø)
...ary/api_schemas_catalog/services_specifications.py 100.0% <ø> (ø)
...odels_library/api_schemas_directorv2/comp_tasks.py 83.6% <100.0%> (ø)
...library/api_schemas_directorv2/dynamic_services.py 96.4% <100.0%> (ø)
...rc/models_library/api_schemas_directorv2/health.py 100.0% <100.0%> (ø)
... and 82 more

@giancarloromeo giancarloromeo added this to the Eisbock milestone Sep 9, 2024
@giancarloromeo giancarloromeo added the t:maintenance Some planned maintenance work label Sep 9, 2024
Copy link
Contributor

@matusdrobuliak66 matusdrobuliak66 left a comment

Choose a reason for hiding this comment

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

Thanks a lot, looks good! 💯 I have added few questions.

Copy link
Member

@sanderegg sanderegg left a comment

Choose a reason for hiding this comment

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

I guess this is still in progress.
There are missing model examples and a =None that might have been added by the migration tool?

Happy to review again once these are in. thanks a lot for the good work!!!

@giancarloromeo
Copy link
Contributor Author

Thanks a lot for your feedback, the PR is still in progress :) All your precious observations are already under control.

@giancarloromeo giancarloromeo changed the title ⬆️ WIP: Upgrade models library ⬆️ WIP: Upgrade models-library Sep 10, 2024
Copy link
Contributor

@GitHK GitHK left a comment

Choose a reason for hiding this comment

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

this is my initial round. I'd like to talk about this PR in person if possible. there are some things that I would like to understand .

Also it's extreenmly hard to review



class ProjectFromCsv(ProjectAtDB):
class Config(ProjectAtDB.Config):
extra = Extra.forbid

# TODO: missing in ProjectAtDB
Copy link
Contributor

Choose a reason for hiding this comment

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

can you please check if this comment has any relevance and just drop it if not?

packages/models-library/tests/test__pydantic_models.py Outdated Show resolved Hide resolved
packages/models-library/tests/test__pydantic_models.py Outdated Show resolved Hide resolved
Comment on lines -144 to -145
with pytest.raises(KeyError, match="missing"):
str(MyErrorBefore(value=42))
Copy link
Contributor

Choose a reason for hiding this comment

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

what about this example? why was it removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

MyErrorBefore inherited from PydanticErrorMixin that in Pydantic v2 changed completely.

See: https://github.com/pydantic/pydantic/blob/cf671c808fba1a78bc4995e8276dd9e48876f2d9/pydantic/errors.py#L72

packages/models-library/tests/test_project_nodes.py Outdated Show resolved Hide resolved
@@ -14,15 +14,20 @@ class MyModel(BaseModel):


def test_schema():
assert MyModel.schema() == {
assert MyModel.model_json_schema() == {
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like your changes broke the test. Are you sure?

Copy link
Member

Choose a reason for hiding this comment

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

Actually this tests is showing that now pydantic NICELY distinguishes nullable fields. NOTE

class MyModel(BaseModel):
    a: int
    b: int | None = Field(...)
    c: int = 42
    d: int | None = None
    e: int = FieldNotRequired(description="optional non-nullable")

And we should get an schema that defines:

  • a is a required int
  • b is a required either int or Nullable
  • c is an optional int that defaults to 42
  • d is an optional either int or Nullable that defaults to None
  • e is an optional int (if not set, it just gives you None)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@GitHK: change is correct.

The method "schema" in class "BaseModel" is deprecated
The schema method is deprecated; use model_json_schema instead.

…/services.py

Co-authored-by: Andrei Neagu <5694077+GitHK@users.noreply.github.com>
Copy link
Member

@pcrespov pcrespov left a comment

Choose a reason for hiding this comment

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

Bravo! I left some comments and questions. Also
On my side this is good enough for a first merge. We can further refine.

if v is None:
cluster_type = values["type"]
default_thumbnails = {
ClusterTypeInModel.AWS.value: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/250px-Amazon_Web_Services_Logo.svg.png",
Copy link
Member

Choose a reason for hiding this comment

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

THOUGHT: wouldn't it make more sense to have links here to assets that we control? they could indeed be a copy of these (if allowed). At least we make sure nobody changes the source
e.g. we have https://github.com/ITISFoundation/osparc-assets repo where we could add default assets link these

@@ -25,7 +22,7 @@ class TaskProgress(BaseModel):
message: ProgressMessage = Field(default="")
percent: ProgressPercent = Field(default=0.0)

@validate_arguments
@validate_call
def update(
self,
Copy link
Member

Choose a reason for hiding this comment

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

I have the impression that here self is also validated. Do we want this? I think there is a way to skip some of the parameters if so.

class FileMetaDataArray(BaseModel):
__root__: list[FileMetaDataGet] = []
class FileMetaDataArray(RootModel[list[FileMetaDataGet]]):
root: list[FileMetaDataGet] = []
Copy link
Member

Choose a reason for hiding this comment

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

THOUGHT: IMO we should get rid of all these root models. They are very confusing. In this case, I would rather annotated list[FileMetaDataGet] than FileMetaDataArray

Comment on lines 133 to 138
AnyUrl = Annotated[str, pydantic.AnyUrl]

AnyHttpUrl = Annotated[str, pydantic.AnyHttpUrl]

HttpUrl = Annotated[str, pydantic.HttpUrl]

Copy link
Member

Choose a reason for hiding this comment

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

Overriding names this way is misleading ...
Either you use different names or force users to use explicit Annotated (my preferred option)

Imaging this scenario:

from models_library.basic_types import AnyUrl
from pydantic import *

class MyModel(BaseModel):
    url : Annotated[str, AnyUrl]


DictKey = TypeVar("DictKey")
DictValue = TypeVar("DictValue")


class DictModel(GenericModel, Generic[DictKey, DictValue]):
__root__: dict[DictKey, DictValue]
class DictModel(RootModel[dict[DictKey, DictValue]], Generic[DictKey, DictValue]):
Copy link
Member

Choose a reason for hiding this comment

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

if ListModel and DictModel are not used anywhere in the codebase, I suggest to drop them and rather use explicit list[MyModel] and dict[str, MyModel] CC @sanderegg , @GitHK @matusdrobuliak66

packages/models-library/src/models_library/progress_bar.py Outdated Show resolved Hide resolved
@@ -14,15 +14,20 @@ class MyModel(BaseModel):


def test_schema():
assert MyModel.schema() == {
assert MyModel.model_json_schema() == {
Copy link
Member

Choose a reason for hiding this comment

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

Actually this tests is showing that now pydantic NICELY distinguishes nullable fields. NOTE

class MyModel(BaseModel):
    a: int
    b: int | None = Field(...)
    c: int = 42
    d: int | None = None
    e: int = FieldNotRequired(description="optional non-nullable")

And we should get an schema that defines:

  • a is a required int
  • b is a required either int or Nullable
  • c is an optional int that defaults to 42
  • d is an optional either int or Nullable that defaults to None
  • e is an optional int (if not set, it just gives you None)

"e": {
"default": None,
Copy link
Member

Choose a reason for hiding this comment

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

I just wonder whether we could change FieldNotRequired to returns some NotSet -like value

@giancarloromeo
Copy link
Contributor Author

giancarloromeo commented Sep 16, 2024

From Pydantic v1 official documentation:

If you want to specify a field that can take a None value while still being required, you can use Optional with ...:

from pydantic import BaseModel, Field, ValidationError


class Model(BaseModel):
    a: int | None
    b: int | None = ...
    c: int | None = Field(...)


print(Model(b=1, c=2))
#> a=None b=1 c=2
try:
    Model(a=1, b=2)
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    c
      field required (type=value_error.missing)
    """

In this model, a, b, and c can take None as a value. But a is optional, while b and c are required. b and c require a value, even if the value is None.

Details: https://docs.pydantic.dev/1.10/usage/models/#required-optional-fields

@giancarloromeo giancarloromeo changed the title ⬆️Upgrade models-library (pydantic v2) ⬆️ Upgrade models-library (pydantic v2) Sep 16, 2024
Copy link
Contributor

@matusdrobuliak66 matusdrobuliak66 left a comment

Choose a reason for hiding this comment

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

🥇

state: ProjectState | None
ui: EmptyModel | StudyUI | None
state: ProjectState | None = None
ui: EmptyModel | StudyUI | None = None
quality: dict[str, Any] = {}
Copy link
Contributor Author

@giancarloromeo giancarloromeo Sep 17, 2024

Choose a reason for hiding this comment

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

@pcrespov If I revert the = None for the fields in this model, the create-project-schemas test fails because the mocked response payload doesn't contain them. Are these fields intended as optional?

Copy link
Member

@pcrespov pcrespov Sep 18, 2024

Choose a reason for hiding this comment

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

@giancarloromeo IMO this case a default =None makes sense

These mocks were generated using real payload from the front-end so it is important that they pass since there is no guarantee that the front-end always follows the openapi specs in the web-api.
Also, as you can see in services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml that these fields are not required:
image

BTW, once all the tests in the models_library pass, you might want to try to re-generate services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml and see if the changes make sense. In principle only the new nullable fields should be update and the rest should be the same. Pay special attention to required fields for instance ...
For that you can use

cd web/server
make openapi-specs

Copy link

sonarcloud bot commented Sep 18, 2024

@giancarloromeo giancarloromeo merged commit d2d81c0 into ITISFoundation:pydantic_v2_migration Sep 18, 2024
27 of 57 checks passed
@giancarloromeo giancarloromeo deleted the is4481/upgrade-models-library branch September 18, 2024 09:50
@giancarloromeo giancarloromeo linked an issue Sep 18, 2024 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
t:maintenance Some planned maintenance work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Major upgrade to pydantic 2.0
5 participants