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

[Tidy] Bump to Pydantic V2 #917

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft

[Tidy] Bump to Pydantic V2 #917

wants to merge 33 commits into from

Conversation

maxschulz-COL
Copy link
Contributor

Description

In draft so far - I tried to keep as clear as possible commit messages - they should tell the whole story.

Screenshot

Notice

  • I acknowledge and agree that, by checking this box and clicking "Submit Pull Request":

    • I submit this contribution under the Apache 2.0 license and represent that I am entitled to do so on behalf of myself, my employer, or relevant third parties, as applicable.
    • I certify that (a) this contribution is my original creation and / or (b) to the extent it is not my original creation, I am authorized to submit this contribution on behalf of the original creator(s) or their licensees.
    • I certify that the use of this contribution as authorized by the Apache 2.0 license does not violate the intellectual property rights of anyone else.
    • I have not referenced individuals, products or companies in any commits, directly or indirectly.
    • I have not added data or restricted code in any commits, directly or indirectly.

…edCallable fields

This for now involves using `from pydantic.json_schema import SkipJsonSchema` which seems like a recommended and stable solution. We can try a more advanced approach if needs be
For now this does not include any arguments, as in the documentation the parent model works on a string type description, and presumably infers the correct model by name. Will need to investigate this behaviour closer, but for now this commit allows unit tests to run!
…y code and use Pydantic v2 imports

This does not include models yet where unit test instantiation fails, mainly due to use of validator etc. It also does not include tests yet
…et checks

This replaces the each_item validation of the old style validator. Tested manually if it generally works
…foreValidator to avoid validator

So far did not fully eliminate validator due to it being present still for Layout - it needs to be determined whether we want a model validator here
This is a larger change that refactors the previous methods of __get_valdators__

The crux of this change is that it was not anymore possible to pass the field info to the validator without having acces to the model class. See more here: pydantic/pydantic#10903
…datate on entire targets list

Additionally change the dev apps a little
At this point, all running code should be free of V1 nad only draw upon V2. This does not means that all docs have been cleaned, or that there is not usage of deprecated V2 functionality

The next big challenge will be to get the add_type correct
This mostly involves fixing changed validation messages. There was also the case that we do NOT coerce numbers into strings anymore (this is technically slightly breaking)
Similar as before, with the change to not coercing strings as the main change
Copy link
Contributor

github-actions bot commented Dec 3, 2024

View the example dashboards of the current commit live on PyCafe ☕ 🚀

Updated on: 2024-12-03 15:53:17 UTC
Commit: ee6722e

Link: vizro-core/examples/dev/

Link: vizro-core/examples/scratch_dev

Link: vizro-core/examples/visual-vocabulary/

Link: vizro-ai/examples/dashboard_ui/

Copy link
Contributor

@antonymilne antonymilne left a comment

Choose a reason for hiding this comment

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

In general it looks 💯 A few comments for now. I'll look at types.py and _base.py tomorrow morning (or whenever you think it's ready).

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess these demo apps need manually checking because we don't have unit tests for them. It looks like they don't do anything complicated with pydantic though so hopefully they just work already.

Dashboard.update_forward_refs(Page=Page, Navigation=Navigation)
NavBar.update_forward_refs(NavLink=NavLink)
NavLink.update_forward_refs(Accordion=Accordion)
Tabs.model_rebuild()
Copy link
Contributor

Choose a reason for hiding this comment

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

If these are removed entirely I presume things don't work?

def set_id(cls, id) -> str:
return id or model_manager._generate_id()

@_log_call
def __init__(self, **data: Any):
def __init__(self, **data: Any): # TODO: model_post_init
Copy link
Contributor

Choose a reason for hiding this comment

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

Yesss 👍 After we do this then hopefully we get much better tab completion of model fields in IDEs. It would be worth checking this and advertising somewhere in the docs (maybe you need to install some plugin).

vizro-core/src/vizro/models/_base.py Show resolved Hide resolved
components: list[_FormComponentType]
layout: Layout = None # type: ignore[assignment]
components: conlist(
Annotated[_FormComponentType, BeforeValidator(check_captured_callable), Field(...)], min_length=1
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the Field(...) really needed here?

Copy link
Contributor

Choose a reason for hiding this comment

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

(same comment applies elsewhere)

components: conlist(
Annotated[_FormComponentType, BeforeValidator(check_captured_callable), Field(...)], min_length=1
) # since no default, can skip validate_default
layout: Optional[Layout] = None # type: ignore[assignment]
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
layout: Optional[Layout] = None # type: ignore[assignment]
layout: Optional[Layout] = None

hopefully?

@@ -31,15 +31,13 @@ class Container(VizroBaseModel):
"""

type: Literal["container"] = "container"
components: list[ComponentType]
components: conlist(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this (and others) done as conlist while others like RangeSlider.value and Container.tabs aren't?

@@ -30,45 +67,20 @@ class Parameter(VizroBaseModel):
"""

type: Literal["parameter"] = "parameter"
targets: list[str] = Field(..., description="Targets in the form of `<target_component>.<target_argument>`.")
targets: Annotated[ # TODO: check if the double annotation is the best way to do this
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks weird now because the each_item=True is handled differently, right?

Ideally there would be a nicer way to do this that leaves the check_dot_notation function etc. inside the Parameter class.

@@ -24,7 +24,7 @@ def validate_min_length(cls, value):
return value


def check_captured_callable(cls, value):
def check_captured_callable(value):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def check_captured_callable(value):
def check_captured_callable_mode(value):

The name of this (which I originally chose) confused me - I think we should rename it.

@@ -26,9 +26,7 @@ class Tabs(VizroBaseModel):
"""

type: Literal["tabs"] = "tabs"
tabs: list[Container]

_validate_tabs = validator("tabs", allow_reuse=True, always=True)(validate_min_length)
Copy link
Contributor

Choose a reason for hiding this comment

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

Remember to remove this validate_min_length (and any other no longer used functions) from the source file.

Copy link
Contributor

@antonymilne antonymilne left a comment

Choose a reason for hiding this comment

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

A few small comments on CapturedCallable but generally looks great 👍

# At this point captured_callable is CapturedCallable or invalid type. Check it is in fact CapturedCallable
# and do final checks:
yield cls._check_type
def __get_pydantic_core_schema__(cls, source: Any, handler: Any) -> cs.core_schema.CoreSchema:
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 remind me why we need this at all?

return cs.core_schema.no_info_plain_validator_function(cls.core_validation)

@staticmethod
def core_validation(value: Any):
Copy link
Contributor

Choose a reason for hiding this comment

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

I see this is called in the above method but when does it actually get used?

Comment on lines +191 to +192
if not isinstance(value, CapturedCallable):
raise ValueError(f"Expected CustomType, got {type(value)}")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if not isinstance(value, CapturedCallable):
raise ValueError(f"Expected CustomType, got {type(value)}")
if not isinstance(value, CapturedCallable):
raise ValueError(f"Expected CapturedCallable, got {type(value)}")

def validate_captured_callable(cls, value, info: ValidationInfo):
# TODO: We may want to double check on the mechanism of how field info is brought to
field_info = cls.model_fields[info.field_name]
return CapturedCallable._validate_captured_callable(value, field_info)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return CapturedCallable._validate_captured_callable(value, field_info)
return CapturedCallable._validate_captured_callable(value, field_info.json_schema_extra])

And rewrite signatures for the validators accordingly (we could even do a TypedDict annotation to show the expected format of json_schema_extra). Unless you think this is a bad idea for some reason. Generally speaking I would pass the minimum required information between these functions.

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.

2 participants