-
Notifications
You must be signed in to change notification settings - Fork 11
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
feat: Add functional API for algorithm contributtions #876
base: develop
Are you sure you want to change the base?
Conversation
Sourcery Code Quality Report❌ Merging this PR will decrease code quality in the affected files by 0.92%.
Here are some functions in these files that still need a tune-up:
Legend and ExplanationThe emojis denote the absolute quality of the code:
The 👍 and 👎 indicate whether the quality has improved or gotten worse with this pull request. Please see our documentation here for details on how these metrics are calculated. We are actively working on this report - lots more documentation and extra metrics to come! Help us improve this quality report! |
Preview page for your plugin is ready here: |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## develop #876 +/- ##
===========================================
+ Coverage 87.84% 90.88% +3.03%
===========================================
Files 151 196 +45
Lines 20484 30464 +9980
===========================================
+ Hits 17995 27688 +9693
- Misses 2489 2776 +287 ☔ View full report in Codecov by Sentry. |
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
…ctional_plugin_api
…ctional_plugin_api
SonarCloud Quality Gate failed. 0 Bugs No Coverage information |
WalkthroughThe changes across various files reflect enhancements in a Python codebase, particularly in a CI/CD pipeline and algorithmic class definitions. New CI steps, class attributes, static methods, and decorators have been introduced, alongside improved error handling and type checking. These updates aim to streamline development workflows, enforce better coding practices, and extend functionality within the codebase. Changes
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (invoked as PR comments)
Additionally, you can add CodeRabbit Configration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 2
Configuration used: CodeRabbit UI
Files ignored due to filter (1)
- setup.cfg
Files selected for processing (11)
- .github/workflows/base_test_workflow.yml (1 hunks)
- package/PartSegCore/algorithm_describe_base.py (5 hunks)
- package/PartSegCore/io_utils.py (3 hunks)
- package/PartSegCore/segmentation/border_smoothing.py (1 hunks)
- package/PartSegCore/segmentation/noise_filtering.py (2 hunks)
- package/PartSegCore/segmentation/threshold.py (4 hunks)
- package/PartSegCore/segmentation/watershed.py (1 hunks)
- package/PartSegCore/utils.py (2 hunks)
- package/tests/test_PartSegCore/segmentation/test_threshold.py (3 hunks)
- package/tests/test_PartSegCore/test_algorithm_describe_base.py (4 hunks)
- package/tests/test_PartSegCore/test_utils.py (2 hunks)
Additional comments: 37
.github/workflows/base_test_workflow.yml (1)
- 115-120: The new "Upload coverage" step has been added to the workflow, which aligns with the PR objectives to enhance the CI/CD pipeline. However, ensure that the
coverage.xml
file is generated before this step is executed, as the hunk does not show the generation of this file.package/PartSegCore/algorithm_describe_base.py (5)
16-25: The addition of imports and the
AlgorithmDescribeNotFound
exception class are correctly implemented and documented.120-126: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [122-130]
The changes to the
AlgorithmDescribeBaseMeta
class's__new__
method correctly handle the new parameters and set class attributes based on the presence of abstract methods.
150-298: The new static methods and the updated
from_function
method in theAlgorithmDescribeBaseMeta
class are correctly implemented to support the creation of classes from functions.307-317: The
AlgorithmDescribeBase
class has been correctly updated with a new__new__
method and aget_doc_from_fields
method to support class creation from functions and generate documentation.328-352: The
from_function
methods in theAlgorithmDescribeBase
class are correctly overloaded to handle different parameter sets for class generation from functions.package/PartSegCore/io_utils.py (4)
103-111: Docstrings added to
need_segmentation
andneed_mask
methods improve code documentation.113-113: The summary mentions refactoring of
get_extensions
method using the walrus operator, but the provided hunk does not show this change.164-169: Refactoring of
get_extensions
method using the walrus operator inLoadBase
class enhances readability.206-211: Modification in
load_metadata_base
to suppress the original exception could impact error handling and debugging.package/PartSegCore/segmentation/border_smoothing.py (3)
14-14: The addition of
method_from_fun="smooth"
to theBaseSmoothing
class definition is consistent with the PR objectives to introduce a functional API for algorithm contributions. This change should be verified to ensure that it integrates correctly with the rest of the functional API infrastructure.15-15: The
__argument_class__
attribute is set toBaseModel
in theBaseSmoothing
class. Ensure that all subclasses ofBaseSmoothing
that require specific argument models override this attribute accordingly.17-17: The
smooth
method inBaseSmoothing
is correctly defined as an abstract class method, which enforces that all subclasses must provide their own implementation. This is a good practice for abstract base classes.package/PartSegCore/segmentation/noise_filtering.py (3)
3-3: The addition of the
abstractmethod
import is correct and necessary for the changes made to theNoiseFilteringBase
class.29-30: The use of
@abstractmethod
decorator onnoise_filter
is appropriate to ensure that subclasses implement this method.32-32: The subclasses of
NoiseFilteringBase
correctly implement thenoise_filter
method, adhering to the contract established by the abstract base class.package/PartSegCore/segmentation/threshold.py (4)
2-8: The import of
mahotas
is correctly added and used within theriddler_calvard
function.40-40: The
BaseThreshold
class now inherits fromAlgorithmDescribeBase
andABC
, which aligns with the PR objective to support algorithm contributions through a functional API.279-280: The
MahotasThreshold
class does not inherit from any base class. This seems inconsistent with the design of other threshold classes in the file, which typically inherit fromBaseThreshold
or another base class. Please verify if this is intentional or an oversight.283-323: The
riddler_calvard
function is correctly implemented and registered inThresholdSelection
, which is in line with the PR objectives.package/PartSegCore/segmentation/watershed.py (1)
- 25-25: The summary mentions the addition of
method_from_fun
as a keyword argument in theBaseWatershed
class definition, but the hunk shows it as a class attribute. This should be corrected in the summary or the code, depending on the intended implementation.package/PartSegCore/utils.py (6)
7-13: The addition of new imports seems appropriate for the new functionality introduced in the file.
468-546: The implementation of the
kwargs_to_model
decorator and its helper functions appears to be correct and follows best practices for preserving function metadata and providing backward compatibility.468-484: The
_get_translation_dicts_from_signature
function correctly extracts translation dictionaries from the function signature and ensures that the parameters haveBaseModel
type annotations.487-493: The
_get_kwargs_from_old_args
function properly maps old positional arguments to keyword arguments based on the providedold_args_order
list.496-505: The
_map_kwargs_to_model
function correctly maps keyword arguments to model instances, ensuring that each keyword argument corresponds to a field in the model.508-546: The
kwargs_to_model
decorator is well-implemented, with clear error handling and a warning for deprecated usage. It ensures backward compatibility while encouraging users to update their function calls.package/tests/test_PartSegCore/segmentation/test_threshold.py (3)
15-21: The explicit specification of
dtype=np.uint32
forsquare
andcube
arrays ensures that the data type is consistent and matches the expected type for the tests.42-42: The addition of type checking for
thr_info
to ensure it is an integer, float, ornp.integer
is a good practice for data type validation in tests.55-56: The type checking for the elements of
thr_info
intest_double_threshold
function ensures that both thresholds returned are of the correct type, which is a good practice for robust testing.package/tests/test_PartSegCore/test_algorithm_describe_base.py (5)
1-6: The addition of
ABC
andabstractmethod
imports suggests that abstract base classes and methods are being used, which is consistent with the PR's objective to enhance the codebase for algorithm contributions.10-16: The import statements are correctly formatted and relevant to the PR's objectives.
519-535: The introduction of
ClassForTestFromFuncBase
andClassForTestFromFunc
with abstract methods aligns with the PR's objective to provide a structured way for developers to contribute new algorithms.359-493: The new test cases added are comprehensive and appear to cover the functionality related to class generation from functions, which is a key part of the PR's objectives.
496-502: The test case for
ROIExtractionProfile
is consistent with the PR's objectives and ensures that the new functional API for algorithm contributions is properly tested.package/tests/test_PartSegCore/test_utils.py (2)
13-18: The addition of
kwargs_to_model
to the import list is consistent with the PR objectives and the summary provided.367-395: The new test function
test_kwargs_to_model
is well-structured and covers various scenarios to ensure thekwargs_to_model
decorator works as expected, including correct usage, deprecation warnings, and errors for incorrect usage.
path: ./coverage.xml | ||
retention-days: 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The retention period for the coverage.xml
artifact is set to 5 days, which is different from the 7-day retention period for other artifacts. Confirm if this inconsistency is intentional.
SonarCloud Quality Gate failed. 0 Bugs No Coverage information Catch issues before they fail your Quality Gate with our IDE extension SonarLint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 9
Configuration used: CodeRabbit UI
Files ignored due to path filters (1)
setup.cfg
is excluded by:!**/*.cfg
Files selected for processing (4)
- package/PartSegCore/algorithm_describe_base.py (5 hunks)
- package/PartSegCore/io_utils.py (5 hunks)
- package/PartSegCore/segmentation/algorithm_base.py (1 hunks)
- package/tests/test_PartSegCore/test_algorithm_describe_base.py (4 hunks)
Additional comments: 20
package/PartSegCore/segmentation/algorithm_base.py (2)
- 207-207: The method
get_info_text
now correctly specifies a return type ofstr
, aligning with best practices for type hinting in Python.- 207-207: Ensure the
calculation_run
method correctly specifies its return type asROIExtractionResult
as per the summary. This change enhances type safety and code readability.Verification successful
The
calculation_run
method correctly specifies its return type asROIExtractionResult
in multiple instances within thePartSegCore
package, as indicated by the search results. This confirms the implementation aligns with the summary's description, enhancing type safety and code readability.Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify the return type of calculation_run method ast-grep --lang python --pattern $'def calculation_run($_, $_) -> ROIExtractionResult { $$$ }'Length of output: 440
Script:
#!/bin/bash # Adjusting the pattern to find the calculation_run method without specifying parameters or return type ast-grep --lang python --pattern $'def calculation_run($$$) { $$$ }'Length of output: 524
Script:
#!/bin/bash # Using ripgrep to search for the calculation_run method in Python files rg "def calculation_run" --type pyLength of output: 2491
package/PartSegCore/io_utils.py (4)
- 71-71: Adding a return type of
str
to theget_short_name
method inSaveBase
andLoadBase
classes improves type clarity and consistency.- 105-105: The addition of documentation for the
need_segmentation
method clarifies its purpose and usage, enhancing code readability.- 110-110: Similarly, adding documentation to the
need_mask
method improves understanding of its necessity in certain save operations.- 166-166: The use of the walrus operator in
get_extensions
method simplifies the code and improves readability by avoiding an extra line of code for the match operation.package/tests/test_PartSegCore/test_algorithm_describe_base.py (14)
- 3-3: The import of
ABC
andabstractmethod
is necessary for defining abstract base classes and methods, aligning with Python's way of enforcing class interfaces.- 13-13: The introduction of
AlgorithmDescribeBaseMeta
in the imports suggests enhancements to metaclass functionalities, potentially for dynamic class creation or attribute enforcement.- 359-377: The tests for generating classes from functions validate the functionality and error handling of the
from_function
method, ensuring robustness and correct behavior.- 379-385: The test for missing return annotation correctly checks for runtime errors when abstract methods in subclasses do not specify return types, enforcing good coding practices.
- 387-398: This test validates that the
from_function
method raises an appropriate error when used with classes that do not support it, ensuring that the method's usage is restricted to compatible classes.- 400-405: The test for passing incorrect types to the
from_function
method ensures type safety and correct parameter handling, contributing to the robustness of the class generation feature.- 407-424: The comprehensive test for the
from_function
method demonstrates its capability to dynamically create classes from functions, validating the feature's core functionality.- 426-435: Testing the
from_function
decorator without explicit parameters ensures flexibility and ease of use in class generation, highlighting the feature's adaptability.- 437-442: This test checks for errors when additional, undefined parameters are passed to the
from_function
method, ensuring strict parameter validation.- 444-449: The test for positional-only arguments in the
from_function
method ensures compatibility with Python's function signature restrictions, maintaining consistency in method definitions.- 451-472: Testing the
from_function
method as a decorator on a class with abstract methods validates its versatility and integration with Python's class system.- 474-485: The test for class generation without user-provided attributes through the
from_function
decorator demonstrates the method's ability to handle minimalistic function definitions.- 487-493: Testing the handling of keyword arguments in functions used with
from_function
ensures that dynamically generated classes can accept and process variable arguments.- 496-500: The test for
ROIExtractionProfile
instantiation and the use ofpytest.warns
to check for deprecation warnings ensures backward compatibility and future-proofing of the API.
""" | ||
Validate if all parameters without default values are defined in self.__calculation_method__ | ||
|
||
:param func: function to validate | ||
:return: set of parameters that should be dropped | ||
""" | ||
signature = inspect.signature(func) | ||
base_method_signature = inspect.signature(method) | ||
take_all = False | ||
|
||
for parameter in signature.parameters.values(): | ||
if parameter.kind in {inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.POSITIONAL_ONLY}: | ||
raise ValueError(f"Function {func} should not have positional only parameters") | ||
if ( | ||
parameter.default is inspect.Parameter.empty | ||
and parameter.name not in base_method_signature.parameters | ||
and parameter.kind != inspect.Parameter.VAR_KEYWORD | ||
): | ||
raise ValueError(f"Parameter {parameter.name} is not defined in {method_name} method") | ||
|
||
if parameter.kind == inspect.Parameter.VAR_KEYWORD: | ||
take_all = True | ||
|
||
if take_all: | ||
return set() | ||
|
||
return { | ||
parameters.name | ||
for parameters in base_method_signature.parameters.values() | ||
if parameters.name not in signature.parameters | ||
} | ||
|
||
@staticmethod | ||
def _get_argument_class_from_signature(func, argument_name: str): | ||
signature = inspect.signature(func) | ||
if argument_name not in signature.parameters: | ||
return BaseModel | ||
return signature.parameters[argument_name].annotation | ||
|
||
@staticmethod | ||
def _get_parameters_from_signature(func): | ||
signature = inspect.signature(func) | ||
return [parameters.name for parameters in signature.parameters.values()] | ||
|
||
def from_function(self, func=None, **kwargs): | ||
"""generate new class from function""" | ||
|
||
# Test if all abstract methods values are provided in kwargs | ||
|
||
if not self.__support_from_function__: | ||
raise RuntimeError("This class does not support from_function method") | ||
|
||
self._validate_if_all_abstract_getters_are_defined(self.__abstract_getters__, kwargs) | ||
|
||
# check if all values have correct type | ||
for key, value in kwargs.items(): | ||
if not isinstance(value, self.__abstract_getters__[key]): | ||
raise TypeError(f"Value for {key} should be {self.__abstract_getters__[key]}") | ||
|
||
def _getter_by_name(name): | ||
def _func(): | ||
return kwargs[name] | ||
|
||
return _func | ||
|
||
parameters_order = self._get_parameters_from_signature(getattr(self, self.__method_name__)) | ||
|
||
def _class_generator(func_): | ||
drop_attr = self._validate_function_parameters( | ||
func_, getattr(self, self.__method_name__), self.__method_name__ | ||
) | ||
|
||
@wraps(func_) | ||
def _calculate_method(*args, **kwargs_): | ||
for attr, name in zip(args, parameters_order): | ||
if name in kwargs_: | ||
raise ValueError(f"Parameter {name} is defined twice") | ||
kwargs_[name] = attr | ||
|
||
for name in drop_attr: | ||
kwargs_.pop(name, None) | ||
return func_(**kwargs_) | ||
|
||
class_dkt = {f"get_{name}": _getter_by_name(name) for name in self.__abstract_getters__} | ||
|
||
class_dkt[self.__method_name__] = _calculate_method | ||
class_dkt["__argument_class__"] = self._get_argument_class_from_signature( | ||
func_, self.__additional_parameters_name__ | ||
) | ||
class_dkt["__from_function__"] = True | ||
|
||
return type(func_.__name__.replace("_", " ").title().replace(" ", ""), (self,), class_dkt) | ||
|
||
if func is None: | ||
return _class_generator | ||
return _class_generator(func) | ||
|
||
|
||
class AlgorithmDescribeBase(ABC, metaclass=AlgorithmDescribeBaseMeta): | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [123-147]
The modifications to the AlgorithmDescribeBaseMeta
metaclass introduce new parameters method_from_fun
and additional_parameters
to the __new__
method. These changes are designed to support the creation of classes from functions, enhancing the flexibility of algorithm definitions. However, there's a potential issue with the logic that determines whether a class supports the from_function
method based on the presence of __method_name__
and __additional_parameters_name__
. This logic may not accurately reflect all scenarios where from_function
could be applicable or beneficial. Additionally, the error handling in the absence of __argument_class__
or get_fields
functions could be more descriptive, providing guidance on how to resolve the issue.
Consider refining the logic that determines the support for from_function
to cover more scenarios and improve the error message when __argument_class__
or get_fields
functions are missing to guide developers on how to make their classes compatible.
@staticmethod | ||
def _get_abstract_getters( | ||
cls2, support_from_function, calculation_method | ||
) -> typing.Tuple[typing.Dict[str, typing.Any], bool]: | ||
abstract_getters = {} | ||
if hasattr(cls2, "__abstractmethods__") and cls2.__abstractmethods__: | ||
# get all abstract methods that starts with `get_` | ||
for method_name in cls2.__abstractmethods__: | ||
if method_name.startswith("get_"): | ||
method = getattr(cls2, method_name) | ||
if "return" not in method.__annotations__: | ||
msg = f"Method {method_name} of {cls2.__qualname__} need to have return type defined" | ||
try: | ||
file_name = inspect.getsourcefile(method) | ||
line = inspect.getsourcelines(method)[1] | ||
msg += f" in {file_name}:{line}" | ||
except TypeError: | ||
pass | ||
raise RuntimeError(msg) | ||
|
||
abstract_getters[method_name[4:]] = getattr(cls2, method_name).__annotations__["return"] | ||
elif method_name != calculation_method: | ||
support_from_function = False | ||
return abstract_getters, support_from_function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _get_abstract_getters
method has been modified to support the new functionality related to creating classes from functions. This method now checks for abstract methods starting with get_
and validates their return type annotations. While this approach ensures that abstract getters are properly defined, it may be too restrictive by excluding potentially valid abstract methods that do not follow this naming convention. Additionally, the error message generated when a return type is not defined could be enhanced to provide more specific guidance on how to correct the issue.
Consider allowing more flexibility in the naming convention of abstract methods and improve the error message for missing return type annotations to offer clearer guidance on resolving the issue.
@staticmethod | ||
def _get_calculation_method_params_name(cls2) -> typing.Optional[str]: | ||
if cls2.__method_name__ is None: | ||
return None | ||
signature = inspect.signature(getattr(cls2, cls2.__method_name__)) | ||
if "arguments" in signature.parameters: | ||
return "arguments" | ||
if "params" in signature.parameters: | ||
return "params" | ||
if "parameters" in signature.parameters: | ||
return "parameters" | ||
raise RuntimeError(f"Cannot determine arguments parameter name in {cls2.__method_name__}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _get_calculation_method_params_name
method attempts to determine the name of the parameter that represents arguments in the calculation method. This method assumes specific parameter names (arguments
, params
, parameters
) and raises a runtime error if none are found. This approach may not accommodate all valid parameter naming conventions used by developers, potentially limiting the flexibility of the API.
Enhance the method to support a wider range of parameter naming conventions or provide a mechanism for developers to specify the parameter name explicitly, improving the API's flexibility.
@staticmethod | ||
def _validate_if_all_abstract_getters_are_defined(abstract_getters, kwargs): | ||
abstract_getters_set = set(abstract_getters) | ||
kwargs_set = set(kwargs.keys()) | ||
|
||
if abstract_getters_set != kwargs_set: | ||
# Provide a nice error message with information about what is missing and is obsolete | ||
missing_text = ", ".join(sorted(abstract_getters_set - kwargs_set)) | ||
if missing_text: | ||
missing_text = f"Not all abstract methods are provided, missing: {missing_text}." | ||
else: | ||
missing_text = "" | ||
extra_text = ", ".join(sorted(kwargs_set - abstract_getters_set)) | ||
if extra_text: | ||
extra_text = f"There are extra attributes in call: {extra_text}." | ||
else: | ||
extra_text = "" | ||
|
||
raise ValueError(f"{missing_text} {extra_text}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _validate_if_all_abstract_getters_are_defined
method checks if all required abstract getters are provided in kwargs
. While this validation is crucial for ensuring that all necessary information is provided, the error message generated when there are missing or extra attributes could be more actionable. Specifically, it could guide developers on how to correct the issue by providing examples or suggesting where to define the missing getters.
Improve the error message generated by _validate_if_all_abstract_getters_are_defined
to include actionable guidance on how to resolve issues with missing or extra attributes.
@staticmethod | ||
def _validate_function_parameters(func, method, method_name) -> set: | ||
""" | ||
Validate if all parameters without default values are defined in self.__calculation_method__ | ||
|
||
:param func: function to validate | ||
:return: set of parameters that should be dropped | ||
""" | ||
signature = inspect.signature(func) | ||
base_method_signature = inspect.signature(method) | ||
take_all = False | ||
|
||
for parameter in signature.parameters.values(): | ||
if parameter.kind in {inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.POSITIONAL_ONLY}: | ||
raise ValueError(f"Function {func} should not have positional only parameters") | ||
if ( | ||
parameter.default is inspect.Parameter.empty | ||
and parameter.name not in base_method_signature.parameters | ||
and parameter.kind != inspect.Parameter.VAR_KEYWORD | ||
): | ||
raise ValueError(f"Parameter {parameter.name} is not defined in {method_name} method") | ||
|
||
if parameter.kind == inspect.Parameter.VAR_KEYWORD: | ||
take_all = True | ||
|
||
if take_all: | ||
return set() | ||
|
||
return { | ||
parameters.name | ||
for parameters in base_method_signature.parameters.values() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _validate_function_parameters
method validates if all parameters without default values are defined in the calculation method. This validation is essential for ensuring compatibility between the function and the class it is being converted into. However, the method currently raises a generic ValueError
for missing parameters, which may not provide enough context for developers to understand the source of the error and how to fix it.
Enhance the error handling in _validate_function_parameters
to provide more specific and actionable error messages, helping developers identify and correct issues with missing parameters more efficiently.
def from_function(self, func=None, **kwargs): | ||
"""generate new class from function""" | ||
|
||
# Test if all abstract methods values are provided in kwargs | ||
|
||
if not self.__support_from_function__: | ||
raise RuntimeError("This class does not support from_function method") | ||
|
||
self._validate_if_all_abstract_getters_are_defined(self.__abstract_getters__, kwargs) | ||
|
||
# check if all values have correct type | ||
for key, value in kwargs.items(): | ||
if not isinstance(value, self.__abstract_getters__[key]): | ||
raise TypeError(f"Value for {key} should be {self.__abstract_getters__[key]}") | ||
|
||
def _getter_by_name(name): | ||
def _func(): | ||
return kwargs[name] | ||
|
||
return _func | ||
|
||
parameters_order = self._get_parameters_from_signature(getattr(self, self.__method_name__)) | ||
|
||
def _class_generator(func_): | ||
drop_attr = self._validate_function_parameters( | ||
func_, getattr(self, self.__method_name__), self.__method_name__ | ||
) | ||
|
||
@wraps(func_) | ||
def _calculate_method(*args, **kwargs_): | ||
for attr, name in zip(args, parameters_order): | ||
if name in kwargs_: | ||
raise ValueError(f"Parameter {name} is defined twice") | ||
kwargs_[name] = attr | ||
|
||
for name in drop_attr: | ||
kwargs_.pop(name, None) | ||
return func_(**kwargs_) | ||
|
||
class_dkt = {f"get_{name}": _getter_by_name(name) for name in self.__abstract_getters__} | ||
|
||
class_dkt[self.__method_name__] = _calculate_method | ||
class_dkt["__argument_class__"] = self._get_argument_class_from_signature( | ||
func_, self.__additional_parameters_name__ | ||
) | ||
class_dkt["__from_function__"] = True | ||
|
||
return type(func_.__name__.replace("_", " ").title().replace(" ", ""), (self,), class_dkt) | ||
|
||
if func is None: | ||
return _class_generator | ||
return _class_generator(func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The from_function
method in the AlgorithmDescribeBaseMeta
class is a significant addition, enabling the generation of new classes from functions. This method includes comprehensive validation and error handling to ensure that the function and provided arguments meet the necessary criteria. However, the method's complexity and the multiple nested functions could impact readability and maintainability. Additionally, the method assumes that the function's name follows a specific naming convention (func_.__name__.replace("_", " ").title().replace(" ", "")
), which may not always be desirable or applicable.
Consider refactoring the from_function
method to simplify its structure and improve readability. Additionally, provide a way for developers to specify the class name explicitly when it cannot be appropriately inferred from the function's name.
def __new__(cls, *args, **kwargs): | ||
if cls.__from_function__: | ||
return getattr(cls, cls.__method_name__)(*args, **kwargs) | ||
return super().__new__(cls) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The __new__
method in the AlgorithmDescribeBase
class has been modified to support instances created from functions. This modification allows for dynamic behavior based on whether the class was generated from a function. While this approach adds flexibility, it also introduces complexity into the instantiation process, potentially affecting the predictability and debuggability of classes derived from AlgorithmDescribeBase
.
Evaluate the impact of the modified __new__
method on the predictability and debuggability of classes derived from AlgorithmDescribeBase
. Consider providing additional documentation or examples to help developers understand and effectively use this new functionality.
@classmethod | ||
def from_function( | ||
cls: TypeT, func=None, **kwargs | ||
) -> typing.Union[TypeT, typing.Callable[[typing.Callable], TypeT]]: | ||
def _from_function(func_) -> typing.Type["AlgorithmDescribeBase"]: | ||
if "name" not in kwargs: | ||
kwargs["name"] = func_.__name__.replace("_", " ").title() | ||
return AlgorithmDescribeBaseMeta.from_function(cls, func_, **kwargs) | ||
|
||
if func is None: | ||
return _from_function | ||
return _from_function(func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The from_function
class method in the AlgorithmDescribeBase
class provides an overload mechanism to support different usage patterns (with or without a specified function). This method leverages the AlgorithmDescribeBaseMeta.from_function
to generate new classes from functions, demonstrating a clear separation of concerns between the metaclass and the class itself. However, the documentation could be improved to better explain the method's overloads and provide examples of how to use each pattern effectively.
Enhance the documentation for the from_function
class method in AlgorithmDescribeBase
to include examples and explanations of the different usage patterns supported by the method's overloads.
@@ -205,7 +206,7 @@ def load_metadata_base(data: typing.Union[str, Path]): | |||
try: | |||
decoded_data = json.loads(str(data), object_hook=partseg_object_hook) | |||
except Exception: # pragma: no cover | |||
raise e # noqa: B904 | |||
raise e from None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying exception handling in load_metadata_base
to use raise e from None
suppresses the context of the original exception, which might hinder debugging. Consider if this change aligns with the desired error handling behavior.
- raise e from None
+ raise e
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
raise e from None | |
raise e |
Summary by CodeRabbit
New Features
AlgorithmDescribeBaseMeta
class to support additional parameters and methods for class creation from functions.Enhancements
need_segmentation
andneed_mask
methods.get_extensions
method for better readability and efficiency.load_metadata_base
function to provide clearer error handling.Bug Fixes
BaseSmoothing
,NoiseFilteringBase
,BaseThreshold
, andBaseWatershed
to correctly incorporate new method attributes.Documentation
Tests