Skip to content

Conversation

@damskii9992
Copy link
Contributor

New pull request, because of weird and overlarge large changelog in #159 after rebasing on the newest develop branch.
This PR implements the changes discussed in ADR #161: A new EasyScience base class to replace BasedBase, ObjectBase, and SerializerComponent, and another base class inheriting from that to implement model-specific functionality.

@damskii9992 damskii9992 added [scope] enhancement Adds/improves features (major.MINOR.patch) [priority] high Should be prioritized soon [area] base classes Changes to or creation of new base classes labels Nov 26, 2025
Copy link
Member

@rozyczko rozyczko left a comment

Choose a reason for hiding this comment

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

minor comments, but the whole structure looks sound

:param skip: List of field names as strings to skip when forming the dictionary
:return: encoded object containing all information to reform an EasyScience object.
"""
serializer = SerializerBase()
Copy link
Member

Choose a reason for hiding this comment

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

can we instantiate SerializerBase just once in the init?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can.
But that would slow down class instantiation. I actually prefer to have it here, as to_dict should only be called once, if even at all. So at best we get a speed-up by only instantiating it when we need it, and at worst we get the same total time. Unless we of course serialize multiple times.
I also thought about having it in the global_object, as we only really need 1 instance of the class globally.
I also thought about making it simple functions, since most of the methods are static functions, which hints that it shouldn't even be a class.

Copy link
Member

Choose a reason for hiding this comment

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

Keeping SerializerBase makes sense - all serialization related methods are in one location and there are enough instance methods to justify it being a separate class. If we take all the static methods away and plop them into say, serialization_utils, we would have to import them separately later and remember about that...

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 only _convert_to_dict and its utility method _recursive_encoder which are not static methods. And both of those could just as easily have been made static methods. The class doesn't even have an __init__ or any attributes . . .

Copy link
Member

Choose a reason for hiding this comment

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

I don't think _convert_to_dict can be easily made static...

_recursive_encoder - yes. but not the other one.

Choose a reason for hiding this comment

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

_convert_to_dict only uses self to later refer where to pull _convert_to_dict and _recursive_encoder from. If we only use these functions from one SerializerBase encoder - they can be static. And not just static but even plain funcitons.

cls_instance = cls(**kwargs)
for key, value in parameter_placeholder.items():
temp_param = getattr(cls_instance, key)
setattr(cls_instance, '_' + key, value)
Copy link
Member

Choose a reason for hiding this comment

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

getattr and setattr could fail. Consider wrapping them in try..except

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They should only fail if we have implemented our classes incorrectly, in which case we should let them fail and throw an error to know about it, or if the serialized dict has been tampered with, in which case other pieces could fail anyways and we should also just let it fail.
Do you have a good reason as to why this could fail otherwise?

Copy link
Member

Choose a reason for hiding this comment

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

e.g. to let users know that the failure stems from this issue. Bubbled exceptions often lose context and it's difficult to pinpoint what happened, if you're in a library code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this (the new code) what you had in mind?

This method is used by the serializer to determine which arguments are needed
by the constructor to deserialize the object.
"""
spec = getfullargspec(self.__class__.__init__)
Copy link
Member

@rozyczko rozyczko Nov 27, 2025

Choose a reason for hiding this comment

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

It seems inspect.signature() is the modern/updated version of getfullargspec. The latter was supposed to be deprecated in >3.7 but was left for backward compatibility. We should move to modern/maintained calls.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I replaced getfullargspec where it was somewhat simple to do an opened an issue on replacing it in the SerializerBases _convert_to_dict method here: #166

@codecov
Copy link

codecov bot commented Nov 27, 2025

Codecov Report

❌ Patch coverage is 88.29114% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.84%. Comparing base (a3a9bcb) to head (7ddaea6).
⚠️ Report is 3 commits behind head on develop.

Files with missing lines Patch % Lines
...yscience/variable/parameter_dependency_resolver.py 63.41% 25 Missing and 5 partials ⚠️
src/easyscience/variable/parameter.py 92.45% 2 Missing and 2 partials ⚠️
src/easyscience/io/serializer_base.py 93.93% 1 Missing and 1 partial ⚠️
src/easyscience/variable/descriptor_number.py 88.88% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #163      +/-   ##
===========================================
+ Coverage    73.06%   80.84%   +7.77%     
===========================================
  Files           47       50       +3     
  Lines         3943     4249     +306     
  Branches       671      739      +68     
===========================================
+ Hits          2881     3435     +554     
+ Misses         868      629     -239     
+ Partials       194      185       -9     
Flag Coverage Δ
unittests 80.84% <88.29%> (?)

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

Files with missing lines Coverage Δ
src/easyscience/base_classes/__init__.py 100.00% <100.00%> (ø)
src/easyscience/base_classes/based_base.py 81.65% <100.00%> (+1.45%) ⬆️
src/easyscience/base_classes/model_base.py 100.00% <100.00%> (ø)
src/easyscience/base_classes/new_base.py 100.00% <100.00%> (ø)
src/easyscience/global_object/map.py 89.41% <100.00%> (+37.31%) ⬆️
src/easyscience/variable/descriptor_number.py 89.57% <88.88%> (+0.65%) ⬆️
src/easyscience/io/serializer_base.py 84.53% <93.93%> (+30.49%) ⬆️
src/easyscience/variable/parameter.py 90.49% <92.45%> (+0.13%) ⬆️
...yscience/variable/parameter_dependency_resolver.py 63.41% <63.41%> (ø)

... and 4 files with indirect coverage changes

:param skip: List of field names as strings to skip when forming the dictionary
:return: encoded object containing all information to reform an EasyScience object.
"""
serializer = SerializerBase()

Choose a reason for hiding this comment

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

_convert_to_dict only uses self to later refer where to pull _convert_to_dict and _recursive_encoder from. If we only use these functions from one SerializerBase encoder - they can be static. And not just static but even plain funcitons.


@display_name.setter
@property_stack
def display_name(self, name: str) -> None:

Choose a reason for hiding this comment

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

it think name hint should be optional

new_obj = self.__class__.from_dict(temp)
return new_obj

def __deepcopy__(self, memo):

Choose a reason for hiding this comment

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

my linter complains for memo not used with the quick fix to del memo in the body of the method

Copy link
Member

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[area] base classes Changes to or creation of new base classes [priority] high Should be prioritized soon [scope] enhancement Adds/improves features (major.MINOR.patch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants