I have the following dataclass:
@dataclass(frozen=True)
class Info:
client_id: str
purchase_id: str
Both these ids are supposed to be UUID's.
I have a method that can check if the arg is UUID:
from uuid import UUID
def check_uuid(arg):
try:
UUID(arg)
except ValueError:
raise argparse.ArgumentTypeError(f"'{arg}' is not a valid UUID")
return arg
I want to define a decorator that I can wrap around my dataclass to perform this check. Something like this:
@check_types
@dataclass(frozen=True)
class Info:
client_id: str
purchase_id: str
Here is what I wrote:
from functools import wraps
import argparse
import inspect
def check_types(callable):
def check_uuid(*args, **kwargs):
for arg in args[1:]
try:
UUID(arg)
except ValueError:
raise argparse.ArgumentTypeError(f"'{arg}' is not a valid UUID")
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
check_uuid(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
if inspect.isclass(callable):
callable.__init__ = decorate(callable.__init__)
return callable
return decorate(callable)
but when I instantiate the class i = Info("123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174000")
I get the following error:
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
I am not sure what I am missing, alternatively, is there a better way I can be type checking for UUIDs when the class is instantiated?
CodePudding user response:
If they are supposed to be UUIDs, then type them as such. The caller should be responsible for parsing any strings before instantiating Info.
@dataclass(frozen=True)
class Info:
client_id: UUID
purchase_id: UUID
x = "..."
y = "..."
i = Info(UUID(x), UUID(y))
You can help the caller by providing a class method that does the type conversion
@dataclass(frozen=True)
class Info:
client_id: UUID
purchase_id: UUID
@classmethod
def from_strings(cls, x, y):
# Let any ValueErrors speak for themselves
x = UUID(x)
y = UUID(y)
return cls(UUID(x), UUID(y))
x = "..."
y = "..."
i = Info.from_strings(x, y)
CodePudding user response:
A viable decorator would be like this (assuming that all the arguments must be UUIDs):
def typecheckedclass(original_class):
orig_init = original_class.__init__
def __init__(self, *args, **kws):
for arg in args:
try:
UUID(arg)
except:
raise ValueError(f"'{arg}' is not a valid UUID")
orig_init(self, *args, **kws)
original_class.__init__ = __init__
return original_class
I catch all the exceptions because catching only ValueError ignores other type of conversion errors. For example if an int is passed as an argument an AttributeError is raised.
