Skip to content

Bump Pydantic to >=2.8 #878

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

Merged
merged 3 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions flow360/component/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ def pass_download_function(self):
if not isinstance(self.case, Case):
raise TypeError("case must be of type Case")

for field_name in self.model_fields:
for field_name in self.__class__.model_fields: # pylint:disable = not-an-iterable
value = getattr(self, field_name)
if isinstance(value, ResultBaseModel):
# pylint: disable=protected-access,no-member
Expand Down Expand Up @@ -897,7 +897,7 @@ def pass_has_functions(self):
"user_defined_dynamics": self.case.has_user_defined_dynamics,
}

for field_name in self.model_fields:
for field_name in self.__class__.model_fields: # pylint:disable = not-an-iterable
value = getattr(self, field_name)
if isinstance(value, ResultBaseModel):
function = has_function_map.get(field_name, lambda: True)
Expand Down Expand Up @@ -1072,7 +1072,7 @@ def set_local_storage(self, local_storage: str, keep_remote_structure: bool = Fa
keep_remote_structure : bool, optional
When true, remote folder structure is assumed to be preserved, otherwise flat structure, by default False
"""
for field_name in self.model_fields:
for field_name in self.__class__.model_fields: # pylint:disable = not-an-iterable
value = getattr(self, field_name)
if isinstance(value, ResultBaseModel):
if keep_remote_structure is True:
Expand Down
2 changes: 1 addition & 1 deletion flow360/component/project_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def _set_up_default_reference_geometry(params: SimulationParams, length_unit: Le
params.reference_geometry = default_reference_geometry
return params

for field in params.reference_geometry.model_fields:
for field in params.reference_geometry.__class__.model_fields:
if getattr(params.reference_geometry, field) is None:
setattr(params.reference_geometry, field, getattr(default_reference_geometry, field))

Expand Down
15 changes: 10 additions & 5 deletions flow360/component/simulation/framework/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ def __pydantic_init_subclass__(cls, **kwargs) -> None:
)

def __setattr__(self, name, value):
if name in self.model_fields:
is_frozen = self.model_fields[name].frozen
# pylint: disable=unsupported-membership-test, unsubscriptable-object
if name in self.__class__.model_fields:
is_frozen = self.__class__.model_fields[name].frozen
if is_frozen is not None and is_frozen is True:
raise ValueError(f"Cannot modify immutable/frozen fields: {name}")
super().__setattr__(name, value)
Expand Down Expand Up @@ -241,6 +242,7 @@ def _get_field_alias(cls, field_name: str = None):
@classmethod
def _get_field_context(cls, info, context_key):
if info.field_name is not None:
# pylint:disable = unsubscriptable-object
field_info = cls.model_fields[info.field_name]
if isinstance(field_info.json_schema_extra, dict):
return field_info.json_schema_extra.get(context_key)
Expand Down Expand Up @@ -283,7 +285,10 @@ def _handle_wrap_validators(cls):
if need_to_rebuild is True:
for mode, method in validators:
info = FieldValidatorDecoratorInfo(
fields=tuple(fields_to_validate), mode=mode, check_fields=None
fields=tuple(fields_to_validate),
mode=mode,
check_fields=None,
json_schema_input_type=Any,
)
deco = Decorator.build(cls, cls_var_name=method, info=info, shim=None)
cls.__pydantic_decorators__.field_validators[method] = deco
Expand Down Expand Up @@ -566,7 +571,7 @@ def _nondimensionalization(

for property_name, value in chain(self_dict.items(), additional_fields.items()):
loc_name = property_name
field = self.model_fields.get(property_name)
field = self.__class__.model_fields.get(property_name)
if field is not None and field.alias is not None:
loc_name = field.alias
if need_conversion(value) and property_name not in exclude:
Expand Down Expand Up @@ -643,7 +648,7 @@ def preprocess(
if property_name in exclude:
continue
loc_name = property_name
field = self.model_fields.get(property_name)
field = self.__class__.model_fields.get(property_name)
if field is not None and field.alias is not None:
loc_name = field.alias
if isinstance(value, Flow360BaseModel):
Expand Down
1 change: 1 addition & 0 deletions flow360/component/simulation/meshing_param/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def invalid_planar_face_tolerance(cls, value):
if validation_info is None:
return value

# pylint:disable = unsubscriptable-object
if (
value != cls.model_fields["planar_face_tolerance"].default
and not validation_info.is_beta_mesher
Expand Down
1 change: 1 addition & 0 deletions flow360/component/simulation/models/volume_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def _disable_expression_for_liquid(cls, value, info: pd.ValidationInfo):
if validation_info is None or validation_info.using_liquid_as_material is False:
return value

# pylint:disable = unsubscriptable-object
if value != cls.model_fields[info.field_name].get_default():
raise ValueError("Expression cannot be used when using liquid as simulation material.")
return value
Expand Down
9 changes: 6 additions & 3 deletions flow360/component/simulation/translator/solver_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,8 +1049,8 @@ def get_solver_json(
# check if all units are flow360:
_ = remove_units_in_dict(dump_dict(op))
translated["freestream"] = {
"alphaAngle": op.alpha.to("degree").v.item() if "alpha" in op.model_fields else 0,
"betaAngle": op.beta.to("degree").v.item() if "beta" in op.model_fields else 0,
"alphaAngle": op.alpha.to("degree").v.item() if "alpha" in op.__class__.model_fields else 0,
"betaAngle": op.beta.to("degree").v.item() if "beta" in op.__class__.model_fields else 0,
"Mach": op.velocity_magnitude.v.item(),
"Temperature": (
op.thermal_state.temperature.to("K").v.item()
Expand All @@ -1064,7 +1064,10 @@ def get_solver_json(
else op.material.dynamic_viscosity.v.item()
),
}
if "reference_velocity_magnitude" in op.model_fields.keys() and op.reference_velocity_magnitude:
if (
"reference_velocity_magnitude" in op.__class__.model_fields.keys()
and op.reference_velocity_magnitude
):
translated["freestream"]["MachRef"] = op.reference_velocity_magnitude.v.item()
op_acoustic_to_static_pressure_ratio = (
(
Expand Down
6 changes: 3 additions & 3 deletions flow360/component/simulation/translator/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,9 @@ def translate_setting_and_apply_to_all_entities(
if class_type and is_exact_instance(obj, class_type):

list_of_entities = []
if "entities" in obj.model_fields:
if "entities" in obj.__class__.model_fields:
if obj.entities is None or (
"stored_entities" in obj.entities.model_fields
"stored_entities" in obj.entities.__class__.model_fields
and obj.entities.stored_entities is None
): # unique item list does not allow None "items" for now.
continue
Expand All @@ -317,7 +317,7 @@ def translate_setting_and_apply_to_all_entities(
list_of_entities = (
obj.entities.items if lump_list_of_entities is False else [obj.entities]
)
elif "entity_pairs" in obj.model_fields:
elif "entity_pairs" in obj.__class__.model_fields:
# Note: This is only used in Periodic BC and lump_list_of_entities is not relavant
if lump_list_of_entities:
raise NotImplementedError(
Expand Down
3 changes: 2 additions & 1 deletion flow360/component/simulation/unit_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,7 @@ class Flow360ConversionUnitSystem(pd.BaseModel):
def __init__(self):
registry = u.UnitRegistry()

for field in self.model_fields.values():
for field in self.__class__.model_fields.values():
if field.json_schema_extra is not None:
target_dimension = field.json_schema_extra.get("target_dimension", None)
if target_dimension is not None:
Expand Down Expand Up @@ -1801,6 +1801,7 @@ def __init__(self):

# pylint: disable=no-self-argument
@pd.field_validator("*")
@classmethod
def assign_conversion_rate(cls, value, info: pd.ValidationInfo):
"""
Pydantic validator for assigning conversion rates to a specific unit in the registry.
Expand Down
4 changes: 2 additions & 2 deletions flow360/component/simulation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ def model_attribute_unlock(model, attr: str):
# validate_assignment is set to False to allow for the attribute to be modified
# Otherwise, the attribute will STILL be frozen and cannot be modified
model.model_config["validate_assignment"] = False
model.model_fields[attr].frozen = False
model.__class__.model_fields[attr].frozen = False
yield
finally:
model.model_config["validate_assignment"] = True
model.model_fields[attr].frozen = True
model.__class__.model_fields[attr].frozen = True


def get_combined_subclasses(cls):
Expand Down
6 changes: 4 additions & 2 deletions flow360/component/simulation/validation/validation_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def extract_literal_values(annotation):
continue
# Get allowed output fields items:
natively_supported = extract_literal_values(
output.output_fields.model_fields["items"].annotation
output.output_fields.__class__.model_fields["items"].annotation
)
allowed_items = natively_supported + additional_fields

Expand All @@ -61,7 +61,9 @@ def extract_literal_values(annotation):
if output.output_type == "IsosurfaceOutput":
# using the 1st item's allowed field as all isosurface have same field definition
allowed_items = (
extract_literal_values(output.entities.items[0].model_fields["field"].annotation)
extract_literal_values(
output.entities.items[0].__class__.model_fields["field"].annotation
)
+ additional_fields
)
for entity in output.entities.items:
Expand Down
14 changes: 9 additions & 5 deletions flow360/plugins/report/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

import os
from typing import Callable, List, Optional, Set, Union
from typing import Annotated, Callable, List, Optional, Set, Union

import pydantic as pd

Expand Down Expand Up @@ -134,6 +134,12 @@ def submit(
return Report(resp["id"])


ReportItemTypes = Annotated[
Union[Summary, Inputs, Table, NonlinearResiduals, Chart2D, Chart3D],
pd.Field(discriminator="type_name"),
]


class ReportTemplate(Flow360BaseModel):
"""
A model representing a report containing various components such as summaries, inputs, tables,
Expand All @@ -151,9 +157,7 @@ class ReportTemplate(Flow360BaseModel):
"""

title: Optional[str] = None
items: List[Union[Summary, Inputs, Table, NonlinearResiduals, Chart2D, Chart3D]] = pd.Field(
discriminator="type_name"
)
items: List[ReportItemTypes] = pd.Field()
include_case_by_case: bool = False
settings: Optional[Settings] = Settings()

Expand All @@ -175,7 +179,7 @@ def check_fig_names(self):
f"Duplicate fig_name '{fig_name}' found in item at index {idx}"
)
used_fig_names.add(fig_name)
# return model
return self

# pylint: disable=protected-access
def _generate_shutter_screenshots(self, context: ReportContext):
Expand Down
Loading