Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write a decorator for a Python dataclass

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?

like image 627
Rakesh Adhikesavan Avatar asked May 23 '26 10:05

Rakesh Adhikesavan


1 Answers

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)
like image 156
chepner Avatar answered May 25 '26 23:05

chepner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!