Reputation: 18725
My class (dataclass
) has many properties that are calculations based on other properties or dataclass
fields.
I'm trying to create a decorator that takes a list of required fields or properties. That means they can't be None
and can't return ValueError
. If any of them is None
then I want to do something - for sake of simplicity let's raise ValueError(f'Missing {fieldname}')
.
def required_fields(required_fields):
def _required_fields(f):
def wrapper(self, *args, **kwargs):
for field in required_fields:
if getattr(self, field) is None:
raise ValueError(f"Missing {field}")
return f
return wrapper
return _required_fields
EDIT - another try
def required_fields(required_fields):
def _required_fields(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
for field in required_fields:
if getattr(self, field) is None:
raise ValueError(f"Missing {field}")
return f(self, *args, **kwargs)
return wrapper
Usage
@dataclasses.dataclass
class LoanCalculator:
_amount: typing.Optional[M] = None
_interest_rate: typing.Optional[M] = None
_years: typing.Optional[M] = None
_balance: typing.Optional[M] = None
_payment_day: typing.Optional[int] = None
_start_date: typing.Optional[datetime.date] = None
class MissingDataError(Exception):
pass
@required_fields(['_interest_rate'])
@property
def monthly_interest_rate(self):
return self._interest_rate / 12
I want to get ValueError(f'Missing _interest_rate')
when it's None
and I call the monthly_interest_rate
.
The problem is that wrapper is not called at all and I don't know how to proceed. Can you give me some hints?
Upvotes: 2
Views: 818
Reputation: 31319
It seems like this is what you're after:
from dataclasses import dataclass
def required_fields(fields):
def wrapper(fun):
def checker(self):
for field in fields:
if not hasattr(self, field):
raise AttributeError(f'Missing field {field}')
if getattr(self, field) is None:
raise ValueError(f'Field {field} is `None`')
return fun(self)
return checker
return wrapper
@dataclass
class LoanCalculator:
_interest_rate: int = None
def set_interest_rate(self, value):
self._interest_rate = value
@property
@required_fields(['_interest_rate'])
def monthly_interest_rate(self):
return self._interest_rate / 12
lc = LoanCalculator()
try:
print(lc.monthly_interest_rate)
except ValueError:
print('This exception is expected')
lc.set_interest_rate(.5) # after this, lc._intereste_rate is no longer None
print(lc.monthly_interest_rate)
print('No exception here')
This decorator checks that the object passed to the method (which happens to be a property setter) has the required attribute, and that its value is not None
.
Output:
This exception is expected
0.041666666666666664
No exception here
The likely answer to your question here may have been: "you should put @property before the @required_fields decorator, not after it"
Upvotes: 2