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

Refactor evolver.base #217

Merged
merged 7 commits into from
Jan 10, 2025
Merged

Conversation

jamienoss
Copy link
Member

@jamienoss jamienoss commented Nov 23, 2024

#213 Was the main driver for this refactor. However, it does not fix that issue but instead refactors some code to make things more manageable to future changes.

The core change is the creation of _BaseModel for the separation of conversion back and forth between Config, instances, and ConfigDescriptors such that it is clearer where in the code this happens - it is no longer circular. For clarity _BaseModel does not handle the conversion between Config and configDescriptor and instead only includes other base conversions, i.e., json strings. Soln. to #234 would also go in this new class. This PR introduces some duplication in logic, but at the expense of it being more direct and explicit per specific desired class behavior.

This should also help and relate to #234.

@jamienoss jamienoss self-assigned this Nov 23, 2024
Copy link

codecov bot commented Nov 23, 2024

Codecov Report

Attention: Patch coverage is 97.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
evolver/base.py 97.14% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Signed-off-by: James Noss <jnoss@jhu.edu>
Signed-off-by: James Noss <jnoss@jhu.edu>
@jamienoss jamienoss force-pushed the 213-fix-nested-configdescriptor-creation branch from 7d14cb2 to ecfda50 Compare January 8, 2025 16:32
Comment on lines -174 to -186
@classmethod
def load(cls, file_path: Path, encoding: str | None = None):
"""Loads the specified config file and return a new instance."""
with open(file_path, encoding=encoding) as f:
return cls.model_validate(yaml.safe_load(f))

def save(self, file_path: Path, encoding: str | None = None):
"""Write out config as yaml file to specified file."""
file_path = Path(file_path)
file_path.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, "w", encoding=encoding) as f:
yaml.dump(self.model_dump(mode="json"), f)

Copy link
Member Author

Choose a reason for hiding this comment

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

These were unnecessarily duplicated.

@jamienoss jamienoss force-pushed the 213-fix-nested-configdescriptor-creation branch from e5ca24c to ab64d08 Compare January 8, 2025 19:39
Signed-off-by: James Noss <jnoss@jhu.edu>
@jamienoss jamienoss force-pushed the 213-fix-nested-configdescriptor-creation branch from ab64d08 to 818accc Compare January 8, 2025 19:45
@jamienoss jamienoss marked this pull request as ready for review January 8, 2025 19:47
@jamienoss jamienoss requested a review from a team as a code owner January 8, 2025 19:47
@jamienoss jamienoss requested a review from amitschang January 8, 2025 19:47
Comment on lines +113 to +126
try:
# json str might be that for a ConfigDescriptor, so try that 1st.
descriptor = ConfigDescriptor.model_validate_json(obj, strict=strict, context=context)
except Exception:
# Ok, maybe it wasn't a ConfigDescriptor... (or was but was malformed)
# Explicitly call ``model_validate_json`` rather than ``super().model_validate()`` to declare explicit
# code logic.
return cls.model_validate_json(obj, strict=strict, context=context)
else:
# Cool, it was a ConfigDescriptor so validate from that. Remember ``ConfigDescriptor.config`` is just a
# dict.
return super().model_validate(
descriptor.config, strict=strict, from_attributes=from_attributes, context=context
)
Copy link
Member Author

@jamienoss jamienoss Jan 9, 2025

Choose a reason for hiding this comment

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

@amitschang It would be handy for #234 if this duplication got nuked. Does it make sense for us to lose info by converting from a descriptor to a naked Config?

It appears that there's currently no test in the suite for this (which I can add), however, I can't think of an existing or needed use case for this, can you? If not, maybe we should nuke it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Test added.

Copy link
Member

Choose a reason for hiding this comment

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

Do you mean a need for converting from a ConfigDescriptor to Config (of the class specified in classinfo, I guess)? If the loss you are talking about is when serializing that Config object we lose the classinfo, then yeah I can't think of a reason we desire this.

But not sure if that is what you are asking.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that's exactly it.

Copy link
Member

Choose a reason for hiding this comment

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

@jamienoss, is there something you will change here, then? Or is it ready?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not in this PR as it opens questions to removing other sections of that switch also, i.e., all other sections dealing with descriptors, not just those as strings.

Sorry for the confusion, I just wanted to know as an aid to working on #234.

Copy link
Member Author

Choose a reason for hiding this comment

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

Opened #242 to address this.

Signed-off-by: James Noss <jnoss@jhu.edu>
Copy link
Member

@amitschang amitschang left a comment

Choose a reason for hiding this comment

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

Looks good overall, just a couple comments

evolver/base.py Outdated
@@ -8,6 +8,7 @@
import pydantic
import pydantic_core
import yaml
from pydantic._internal._validators import import_string
Copy link
Member

Choose a reason for hiding this comment

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

Should we be using this? the _internal and lack of docs make me think not. It looks like this is used to import from fqcn to a type, perhaps we could either make a util using importlib or a simple wrapper around a pydantic model that has ImportString type?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking at https://github.com/pydantic/pydantic/blob/main/pydantic/_internal/_validators.py#L72, it doesn't seem worth it to replicate this. Your second suggestion sound great though and will implement that, thanks!

evolver/base.py Outdated
validate_classinfo(descriptor.classinfo)
config = descriptor.config

config_dict = config.shallow_model_dump() if isinstance(config, _BaseConfig) else config
Copy link
Member

Choose a reason for hiding this comment

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

It might be worth a note here for posterity as to why this is needed - is it because this could be a dict due to 287 or 301? In the latter case, should it be validated or has it already been?

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment added. Also, note that this should be checked against _BaseModel and not _BaseConfig.

Copy link
Member Author

Choose a reason for hiding this comment

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

...and it will have been validated by then.

Signed-off-by: James Noss <jnoss@jhu.edu>
Signed-off-by: James Noss <jnoss@jhu.edu>
@jamienoss jamienoss requested a review from amitschang January 10, 2025 17:57
@jamienoss jamienoss enabled auto-merge (squash) January 10, 2025 17:58
# Next handle dict | str, which could be that representing a Config or a ConfigDescriptor.
# First see if config is actually a descriptor by trying to create one, since it's only fields are limited
# to classinfo and config.
# Last is a str representing either a Config or ConfigDescriptor.
try:
descriptor = ConfigDescriptor.model_validate(config, context=dict(extra="forbid"))
Copy link
Member Author

Choose a reason for hiding this comment

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

Re #217 (comment), validation takes place here.

elif _is_descriptor_dict(config):
validate_classinfo(config["classinfo"])
config = cls.Config.model_validate(config["config"])
elif isinstance(config, dict):
Copy link
Member Author

Choose a reason for hiding this comment

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

Re #217 (comment), validation takes place here.

@jamienoss jamienoss merged commit 600f130 into main Jan 10, 2025
8 checks passed
@jamienoss jamienoss deleted the 213-fix-nested-configdescriptor-creation branch January 10, 2025 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants