spdx_tools.common.typing.dataclass_with_properties
1# SPDX-FileCopyrightText: 2022 spdx contributors 2# 3# SPDX-License-Identifier: Apache-2.0 4from dataclasses import dataclass 5 6from beartype import beartype 7from beartype.roar import BeartypeCallHintException 8 9 10def dataclass_with_properties(cls): 11 """Decorator to generate a dataclass with properties out of the class' value:type list. 12 Their getters and setters will be subjected to the @typechecked decorator to ensure type conformity.""" 13 data_cls = dataclass(cls) 14 for field_name, field_type in data_cls.__annotations__.items(): 15 set_field = make_setter(field_name, field_type) 16 get_field = make_getter(field_name, field_type) 17 18 setattr(data_cls, field_name, property(get_field, set_field)) 19 20 return data_cls 21 22 23def make_setter(field_name, field_type): 24 """helper method to avoid late binding when generating functions in a for loop""" 25 26 @beartype 27 def set_field(self, value: field_type): 28 setattr(self, f"_{field_name}", value) 29 30 def set_field_with_error_conversion(self, value: field_type): 31 try: 32 set_field(self, value) 33 except BeartypeCallHintException as err: 34 error_message: str = f"SetterError {self.__class__.__name__}: {err}" 35 36 # As setters are created dynamically, their argument name is always "value". We replace it by the 37 # actual name so the error message is more helpful. 38 raise TypeError(error_message.replace("value", field_name, 1) + f": {value}") 39 40 return set_field_with_error_conversion 41 42 43def make_getter(field_name, field_type): 44 """helper method to avoid late binding when generating functions in a for loop""" 45 46 def get_field(self) -> field_type: 47 return getattr(self, f"_{field_name}") 48 49 return get_field
def
dataclass_with_properties(cls):
11def dataclass_with_properties(cls): 12 """Decorator to generate a dataclass with properties out of the class' value:type list. 13 Their getters and setters will be subjected to the @typechecked decorator to ensure type conformity.""" 14 data_cls = dataclass(cls) 15 for field_name, field_type in data_cls.__annotations__.items(): 16 set_field = make_setter(field_name, field_type) 17 get_field = make_getter(field_name, field_type) 18 19 setattr(data_cls, field_name, property(get_field, set_field)) 20 21 return data_cls
Decorator to generate a dataclass with properties out of the class' value:type list. Their getters and setters will be subjected to the @typechecked decorator to ensure type conformity.
def
make_setter(field_name, field_type):
24def make_setter(field_name, field_type): 25 """helper method to avoid late binding when generating functions in a for loop""" 26 27 @beartype 28 def set_field(self, value: field_type): 29 setattr(self, f"_{field_name}", value) 30 31 def set_field_with_error_conversion(self, value: field_type): 32 try: 33 set_field(self, value) 34 except BeartypeCallHintException as err: 35 error_message: str = f"SetterError {self.__class__.__name__}: {err}" 36 37 # As setters are created dynamically, their argument name is always "value". We replace it by the 38 # actual name so the error message is more helpful. 39 raise TypeError(error_message.replace("value", field_name, 1) + f": {value}") 40 41 return set_field_with_error_conversion
helper method to avoid late binding when generating functions in a for loop
def
make_getter(field_name, field_type):
44def make_getter(field_name, field_type): 45 """helper method to avoid late binding when generating functions in a for loop""" 46 47 def get_field(self) -> field_type: 48 return getattr(self, f"_{field_name}") 49 50 return get_field
helper method to avoid late binding when generating functions in a for loop