0

I want to validate if a user initialized the following Test class correctly by using decorators. It's the first time I am using decorators, therefore I am a bit unsure about them. The validation criterion is that all the integers must be lower than 11, but there may be some strings or other arguments, where the if statement could fail. Because of the for loop the "critical" arguments (int) must be passed at first. Otherwise the TypeError exception will return func. That is what I got so far, but I am not happy with it:

def validate_max(func):
    """Decorator to validate a function passed as argument.

    Args:
        func (function): Function to validate.
    """
    def wrapper(*args, **kwargs):
        # throw away the first argument, because it will be the instance itself
        arguments = [arg for arg in args if args.index(arg) != 0]
        for arg in arguments:
            # try statement, because you may expect a str as one constructor argument
            try:
                if arg > 10:
                    raise ValueError("One or more arguments are higher than 10")
            except TypeError:
                return func(*args, **kwargs)
    return wrapper


class Test:
    @validate_max
    def __init__(self, x, y, name):
        """Constructor of Test class

        Args:
            x (int): An integer.
            y (int): An integer.
            name (str): A string.
        """
        self.x = x
        self.y = y
        self.name = name



if __name__ == "__main__":
    t = Test(1, 20, "hi")

If you see something else you would handle differently, please let me now.

6
  • 1
    Does it work? What exactly is the problem? Commented Nov 9, 2020 at 11:48
  • Yes it works, but if I do "t = Test(1, 2, "hi", 20)" -> this won't throw the ValueError, because 20 comes after "hi" Commented Nov 9, 2020 at 11:50
  • A side note, what is this weird creation for arguments? Why do you even need that check, and even if you do, why not just arguments = args[1:]? Commented Nov 9, 2020 at 11:52
  • That should be a list comprehension, but your solution is WAY better :D Commented Nov 9, 2020 at 11:58
  • 1
    @juanpa.arrivillaga I changed it, thanks. You are right! Commented Nov 9, 2020 at 12:02

2 Answers 2

1

Right now you call the function after encountering the first non-int argument. It sounds like you want to only check ints, and other types are basically don't care. Only if all arguments are either not ints or smaller than 11, call the function:

from itertools import chain

def validate_max(func):
    def wrapper(*args, **kwargs):
        for arg in chain(args, kwargs.values()):
            # try statement, because you may expect a str as one constructor argument
            try:
                if arg > 10:
                    raise ValueError("One or more arguments are higher than 10")
            except TypeError:
                pass
        return func(*args, **kwargs)
    return wrapper

A more compact version that follows the description can be:

def validate_max(func):
    def wrapper(*args, **kwargs):
        if all(arg <= 10 for arg in chain(args, kwargs.values()) if isinstance(arg, int)):
            return func(*args, **kwargs)
        raise ValueError("One or more arguments are higher than 10")
    return wrapper
Sign up to request clarification or add additional context in comments.

Comments

0

I am not sure what is your question. if you are asking for best practice for this kind of validation so I will recommend dropping the decorator here and use type validation with pydantic. you can declare a schema class as follow:

from pydantic import BaseModel, 
class Coordinates(BaseModel):
     x: int = Field(...,lt=11)
     y: int = Field(...,lt=11)

class Test:
   def __init__(self, coor = Coordinates, name: str):
        self.x = coor.x
        self.y = coor.y
        self.name = name

1 Comment

@Tomerikoo solved the problem, but yes, I am always looking for best practice. I will have a look at pydantic. It seems very useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.