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

Pydantic Evaluations Conversion #388

Merged

Conversation

christophertubbs
Copy link
Contributor

closes #261

Converts Evaluation Specification classes to Pydantic models.

@christophertubbs
Copy link
Contributor Author

All that's really left is to ensure that everything works through the entire evaluation service pipeline.

@christophertubbs christophertubbs added the enhancement New feature or request label Jul 12, 2023
@christophertubbs christophertubbs marked this pull request as ready for review July 13, 2023 20:01
Copy link
Member

@aaraney aaraney left a comment

Choose a reason for hiding this comment

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

Great stuff, @christophertubbs! I left a few minor comments that should be pretty straight forward to get through. Thanks!

python/lib/core/dmod/core/common/helper_functions.py Outdated Show resolved Hide resolved
python/lib/core/dmod/core/common/types.py Outdated Show resolved Hide resolved
python/lib/core/dmod/core/common/types.py Outdated Show resolved Hide resolved
fields = {
"properties": self.properties.copy() if self.properties else dict()
}
if exclude_defaults is None:
Copy link
Member

Choose a reason for hiding this comment

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

why not just set the parameter default values to the default that you are assigning here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a basic protection approach. I found one page describing some of it (here)[https://medium.com/python-features/how-to-avoid-classic-pitfall-while-passing-default-values-in-python-7002c0dc4c7c]. It's more important for objects, but it's a protection approach here because None is an invalid, but acceptable input. I can go ahead and remove it for primitive values if you'd like.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, default object types are a for sure no-no in python, but "copy" types in python are totally fair to use as defaults and I think make the code more clear. If you feel obliged to change it, go for it! But im not going to hold this up over something this small!

def apply_configuration(
self,
configuration: typing.Dict[str, typing.Any],
template_manager: TemplateManager,
decoder_type: typing.Type[json.JSONDecoder] = None
):
self.__where = configuration.get("where", self.__where)
self.__datatype = configuration.get("datatype", self.__datatype)
self.where = configuration.get("where", self.where)
Copy link
Member

Choose a reason for hiding this comment

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

This feels like it could lead to unexpected behavior. I know if I were testing this, I would provide the wrong type just to see what happens. It feels like the most sane thing to do is to check if it exists and is a str then allow the mutation, otherwise throw. The same for datatype. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self.where = configuration.get("where", self.where)
if "where" in configuration:
if not isinstance(configuration["where"], str):
raise TypeError("value of key 'where' must be type str.")
self.where = configuration["where"]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to mimic the provided logic for datatype, but changed the logic for where to make sure the given value matches the supplied literal values in the where definition

def __eq__(self, other: "SchemeSpecification") -> bool:
metric_functions: typing.List[MetricSpecification] = Field(
description="The metrics to perform within the evaluation",
alias="metrics"
Copy link
Member

Choose a reason for hiding this comment

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

Just pointing out, what can be a gotcha with pydantic < 2. alias field names do not propogate to subclasses unless you define them in Config.fields (i.e. fields = {"metric_functions": "metrics"}). I didnt see where this was being inherited from, so it is not an issue now, but just something to watch out for.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. This might bite us soon since it looks like Config classes are being deprecated.

Copy link
Member

@aaraney aaraney left a comment

Choose a reason for hiding this comment

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

Thanks for making the requested changes! I have a few comments that should be pretty easy to work through and then this can be on its way! Thanks again, @christophertubbs!

@@ -68,8 +68,34 @@ def apply_configuration(
template_manager: TemplateManager,
decoder_type: typing.Type[json.JSONDecoder] = None
):
self.from_field = configuration.get("from_field", self.from_field)
self.pattern = configuration.get("pattern", self.pattern)
if "from_field" in configuration:
Copy link
Member

Choose a reason for hiding this comment

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

Similar to my comment in the dmod.evaluations.specification.fields module, could this be simplified by creating a new instance of LocationSpecification from the configuration parameter and mutating self using the new instances parameters? Im probably missing something. I feel like is something you would have done if it were plausible here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

LocationSpecification is one of the few classes where this could work considering that all of its fields are optional (i.e. just about any combination is a-ok). What would that look like? Just something like:

location_template = self.__class__.parse_obj(configuration)

if location_template.from_field is not None:
    # assign

# etc

?

Copy link
Member

Choose a reason for hiding this comment

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

location_template = self.__class__.parse_obj(configuration)

if location_template.from_field is not None:
    # assign

# etc

you could even just do this:

location_template = type(self).parse_obj(configuration)

aaraney
aaraney previously approved these changes Aug 4, 2023
Copy link
Member

@aaraney aaraney left a comment

Choose a reason for hiding this comment

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

Thanks for making the requested changes, @christophertubbs! Merge at will!

…' to 'backend_type' for Backend specifications and converted evaluation specification objects to Pydantic
… TypeDefinition.matches that used the old `complies` nomenclature.
…o read and changed the type in the ValueSelector to be one of several optional string values rather than ANY string
…ere' and 'datatype' in a specification configuration
…eld` and updated the overriding logic for `from_field` and `pattern` in `LocationSpecification` to ensure that bytes are sufficiently decoded and string types are enforced.
…eld` and updated the overriding logic for `from_field` and `pattern` in `LocationSpecification` to ensure that bytes are sufficiently decoded and string types are enforced.
…nd added a TODO to the disk data_retriever to inform that the 'date_parser' logic needs to be replaced
…e specification before trying to reference it
@christophertubbs christophertubbs merged commit ec17507 into NOAA-OWP:master Aug 7, 2023
1 check passed
@christophertubbs christophertubbs deleted the evaluation_spec_to_pydantic branch October 19, 2023 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add PyDantic to evaluation specifications
2 participants