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 API Server service (Pydantic v2) #6580

Conversation

giancarloromeo
Copy link
Contributor

@giancarloromeo giancarloromeo commented Oct 23, 2024

What do these changes do?

Upgrade the API Server service to Pydantic v2.

Related issue/s

How to test

make devenv
source .venv/bin/activate
cd services/api-server
make install-dev
make mypy
make test-dev-unit

Dev-ops checklist

@giancarloromeo giancarloromeo self-assigned this Oct 23, 2024
@giancarloromeo giancarloromeo added the a:apiserver api-server service label Oct 23, 2024
@giancarloromeo giancarloromeo added this to the MartinKippenberger milestone Oct 23, 2024
Copy link

codecov bot commented Oct 23, 2024

Codecov Report

Attention: Patch coverage is 89.54248% with 16 lines in your changes missing coverage. Please review.

Project coverage is 81.9%. Comparing base (fb48eb1) to head (fd1ae8d).
Report is 3 commits behind head on pydantic_v2_migration_do_not_squash_updates.

Files with missing lines Patch % Lines
...simcore_service_api_server/services/director_v2.py 55.5% 4 Missing ⚠️
...src/simcore_service_api_server/services/storage.py 25.0% 3 Missing ⚠️
...src/simcore_service_api_server/api/routes/files.py 66.6% 1 Missing ⚠️
...er/exceptions/handlers/_httpx_client_exceptions.py 50.0% 1 Missing ⚠️
...erver/exceptions/handlers/_log_streaming_errors.py 50.0% 1 Missing ⚠️
...i_server/exceptions/handlers/_validation_errors.py 0.0% 1 Missing ⚠️
...simcore_service_api_server/models/schemas/files.py 87.5% 1 Missing ⚠️
...mcore_service_api_server/models/schemas/solvers.py 85.7% 1 Missing ⚠️
...mcore_service_api_server/services/log_streaming.py 80.0% 1 Missing ⚠️
...pi_server/services/solver_job_models_converters.py 88.8% 1 Missing ⚠️
... and 1 more
Additional details and impacted files

Impacted file tree graph

@@                              Coverage Diff                              @@
##           pydantic_v2_migration_do_not_squash_updates   #6580     +/-   ##
=============================================================================
+ Coverage                                         80.9%   81.9%   +0.9%     
=============================================================================
  Files                                              289     512    +223     
  Lines                                            10605   19536   +8931     
  Branches                                           346    1039    +693     
=============================================================================
+ Hits                                              8584   16007   +7423     
- Misses                                            1975    3365   +1390     
- Partials                                            46     164    +118     
Flag Coverage Δ
unittests 81.9% <89.5%> (+0.9%) ⬆️

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

Files with missing lines Coverage Δ
...c/simcore_service_api_server/api/routes/solvers.py 63.7% <100.0%> (ø)
...core_service_api_server/api/routes/solvers_jobs.py 100.0% <ø> (ø)
...vice_api_server/api/routes/solvers_jobs_getters.py 93.3% <100.0%> (ø)
...c/simcore_service_api_server/api/routes/studies.py 100.0% <100.0%> (ø)
...src/simcore_service_api_server/core/application.py 92.9% <100.0%> (ø)
...er/src/simcore_service_api_server/core/settings.py 100.0% <100.0%> (ø)
...imcore_service_api_server/db/repositories/users.py 100.0% <100.0%> (ø)
...src/simcore_service_api_server/exceptions/_base.py 100.0% <100.0%> (ø)
...e_api_server/exceptions/handlers/_custom_errors.py 84.6% <100.0%> (ø)
...er/exceptions/handlers/_handlers_backend_errors.py 100.0% <100.0%> (ø)
... and 23 more

... and 241 files with indirect coverage changes

@giancarloromeo giancarloromeo changed the title WIP: ⬆️ Upgrade API Server service WIP: ⬆️ Upgrade API Server service (Pydantic v2) Oct 23, 2024
@giancarloromeo giancarloromeo added the t:maintenance Some planned maintenance work label Oct 23, 2024
@giancarloromeo giancarloromeo changed the title WIP: ⬆️ Upgrade API Server service (Pydantic v2) ⬆️ Upgrade API Server service (Pydantic v2) Oct 23, 2024
@giancarloromeo giancarloromeo marked this pull request as ready for review October 23, 2024 09:44
@giancarloromeo giancarloromeo requested a review from GitHK October 23, 2024 09:44
workspace_id: WorkspaceID | None
folder_id: FolderID | None
permalink: ProjectPermalink | None = FieldNotRequired()
workspace_id: WorkspaceID | None = None
Copy link
Contributor Author

@giancarloromeo giancarloromeo Oct 23, 2024

Choose a reason for hiding this comment

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

@matusdrobuliak66 confirmed that workspace_id and folder_id are optional

Copy link
Member

Choose a reason for hiding this comment

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

these are only for jsonschema generation right? what confuses me is when you say "optional" this does not mean with a default of None


_empty_description = field_validator("description", mode="before")(
none_to_empty_str_pre_validator
)

model_config = ConfigDict(
frozen=False
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Superclass is frozen and the behaviour was inherited.

Copy link
Member

Choose a reason for hiding this comment

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

thanks! I guess these went off with the re-creation of the branch. how did you find that out? in the warnings of pytest??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a test there was a direct assignment of a field in that model.

@@ -21,6 +21,10 @@ class WalletGet(OutputSchema):
created: datetime
modified: datetime

model_config = ConfigDict(
frozen=False
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Superclass is frozen and the behaviour was inherited.

Copy link
Member

Choose a reason for hiding this comment

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

same question

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a test there was a direct assignment of a field in that model.

@@ -15,8 +15,8 @@ reqs: ## compiles pip requirements (.in -> .txt)


# specification of the used openapi-generator-cli (see also https://github.com/ITISFoundation/openapi-generator)
OPENAPI_GENERATOR_NAME := itisfoundation/openapi-generator-cli-openapi-generator-v4.2.3
OPENAPI_GENERATOR_TAG := v0
OPENAPI_GENERATOR_NAME := openapitools/openapi-generator-cli
Copy link
Contributor Author

@giancarloromeo giancarloromeo Oct 23, 2024

Choose a reason for hiding this comment

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

For OpenAPI Spec validation we use the official tool, our fork is obsolete and doesn't support OpenAPI Spec 3.1.x.

Copy link
Member

Choose a reason for hiding this comment

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

@bisgaard-itis can you check now this openapi.json generates the client? Probably is going to force us to go to the next version of the generator

Copy link
Contributor

Choose a reason for hiding this comment

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

I will give it a try

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!

Copy link
Contributor

@bisgaard-itis bisgaard-itis left a comment

Choose a reason for hiding this comment

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

Very cool! Thanks a lot for the effort. Just a few questions, mainly for my understanding

minimum: int | float | None = None
anyOf: list["CapturedParameterSchema"] | None = None
allOf: list["CapturedParameterSchema"] | None = None
oneOf: list["CapturedParameterSchema"] | None = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity: Why do these have default values now? I believe this is used quite extensively for our testing, so I would try to be as strict as possible in our model.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

but still. these were thought as "required" but can be "none" and now they are "non-required". we already had that discussion if I remember well. you showed that pydantic v1 was misbehaving. but still the intent was of having them "required". I would double check with @bisgaard-itis and @pcrespov here

OPENAPI_GENERATOR_NAME := itisfoundation/openapi-generator-cli-openapi-generator-v4.2.3
OPENAPI_GENERATOR_TAG := v0
OPENAPI_GENERATOR_NAME := openapitools/openapi-generator-cli
OPENAPI_GENERATOR_TAG := latest
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to pin the version? I believe this is used in the tests (checking compatibility of openapi specs) and not pinning the version creates a "loose end". If suddenly a bug/change is introduced in the tool we may start seeing strange compatibility failures in our ci

assert request # nosec
assert isinstance(exc, BaseBackEndError)

Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity: What's the motivation for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mypy complained about function signature assigned to app.add_exception_handler.

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.

thanks!

workspace_id: WorkspaceID | None
folder_id: FolderID | None
permalink: ProjectPermalink | None = FieldNotRequired()
workspace_id: WorkspaceID | None = None
Copy link
Member

Choose a reason for hiding this comment

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

these are only for jsonschema generation right? what confuses me is when you say "optional" this does not mean with a default of None


_empty_description = field_validator("description", mode="before")(
none_to_empty_str_pre_validator
)

model_config = ConfigDict(
frozen=False
Copy link
Member

Choose a reason for hiding this comment

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

thanks! I guess these went off with the re-creation of the branch. how did you find that out? in the warnings of pytest??

@@ -21,6 +21,10 @@ class WalletGet(OutputSchema):
created: datetime
modified: datetime

model_config = ConfigDict(
frozen=False
Copy link
Member

Choose a reason for hiding this comment

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

same question

minimum: int | float | None = None
anyOf: list["CapturedParameterSchema"] | None = None
allOf: list["CapturedParameterSchema"] | None = None
oneOf: list["CapturedParameterSchema"] | None = None
Copy link
Member

Choose a reason for hiding this comment

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

but still. these were thought as "required" but can be "none" and now they are "non-required". we already had that discussion if I remember well. you showed that pydantic v1 was misbehaving. but still the intent was of having them "required". I would double check with @bisgaard-itis and @pcrespov here

anyOf = values.get("anyOf")
allOf = values.get("allOf")
oneOf = values.get("oneOf")
type_ = values.type_
Copy link
Member

Choose a reason for hiding this comment

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

this should be a root_validator or the pydantic v2 pendant instead. instead of playing with strings, you would have the self.pattern

@@ -334,7 +334,7 @@ async def get_job_output_logfile(
f"{solver_key}/releases/{version}/jobs/{job_id}/outputs/logfile",
presigned_download_link,
)
return RedirectResponse(presigned_download_link)
return RedirectResponse(f"{presigned_download_link}")
Copy link
Member

Choose a reason for hiding this comment

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

interesting that fastapi that is using pydantic v2 would not be compatible with new style URL? are you sure?

Copy link
Contributor Author

@giancarloromeo giancarloromeo Oct 24, 2024

Choose a reason for hiding this comment

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

At the end it would work, because internally there the Url is stringified... but type-checking complains.

src/simcore_service_api_server/api/routes/solvers_jobs_getters.py: note: In function "get_job_output_logfile":
src/simcore_service_api_server/api/routes/solvers_jobs_getters.py:337:33: error: Argument 1 to "RedirectResponse" has incompatible type "Url"; expected "str | URL"  [arg-type]
Found 1 error in 1 file (checked 90 source files)
class RedirectResponse(
    url: str | URL,    # starlette.datastructures.URL
    status_code: int = 307,
    headers: Mapping[str, str] | None = None,
    background: BackgroundTask | None = None
)

@@ -70,10 +68,9 @@ class JobInputs(BaseModel):

# TODO: gibt es platz fuer metadata?
Copy link
Member

Choose a reason for hiding this comment

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

@pcrespov @bisgaard-itis German TODO... that is a good one

model_config = ConfigDict(
frozen=False
)


class WalletGetWithAvailableCredits(WalletGet):
available_credits: Decimal
Copy link
Contributor

Choose a reason for hiding this comment

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

N.B. as discussed offline with @giancarloromeo pydantic 2 has a breaking change concerning how it deals with Decimals which will affect the behaviour of the server (pydantic/pydantic#7120 (comment)). That means once this PR gets merged users will receive, say {"my_decimal": "1.23"} instead of {"my_decimal": 1.23}. This will probably affect users who use the osparc client.

Copy link
Contributor Author

@giancarloromeo giancarloromeo Oct 24, 2024

Choose a reason for hiding this comment

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

Checking for solution to make this back-compatible with the old behavior 👌 I read that using json_encoders is discouraged, because it will change...

Copy link
Contributor Author

@giancarloromeo giancarloromeo Oct 24, 2024

Choose a reason for hiding this comment

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

This change solves our problems:

ad0cf2b

OpenAPI Spec file generated again using make openapi.json command.

Copy link
Contributor

Choose a reason for hiding this comment

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

Very cool! Thanks a lot for the quick fix

Copy link

@giancarloromeo giancarloromeo merged commit 39aed6a into ITISFoundation:pydantic_v2_migration_do_not_squash_updates Oct 25, 2024
40 of 58 checks passed
@giancarloromeo giancarloromeo deleted the is4481/upgrade-api-server-service branch November 21, 2024 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:apiserver api-server service t:maintenance Some planned maintenance work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants