Home > database >  What is the best way to validate attributes of a class that have many attributes in Python?
What is the best way to validate attributes of a class that have many attributes in Python?

Time:04-25

I'm working in an API and I have to validate the data that users write in a post/put endpoint. But my model class has 10 attributes, so I have to validate them with 10 ifs, so it makes my code look terrible.

Example:

class MyClass():

    atr1: str
    atr2: str
    atr3: str
    atr4: str
    atr5: str
    atr6: str
    atr7: str
    atr8: str
    atr9: str
    atr10: str

And then I have my put endpoint:

@api.put("/update/{id}")
def update(id: int, exemple: MyClass):
    if exemple not in exemples:
        return {"Error": "exemple not found!"}

    if exemple.atr1 != None:
        exemples[exemple].atr1 = exemple.atr1
    if exemple.atr2 != None:
        exemples[exemple].atr2 = exemple.atr2
    if exemple.atr3 != None:
        exemples[exemple].atr3 = exemple.atr3
    if exemple.atr4 != None:
        exemples[exemple].atr4 = exemple.atr4
    if exemple.atr5 != None:
        exemples[exemple].atr5 = exemple.atr5
    if exemple.atr6 != None:
        exemples[exemple].atr6 = exemple.atr6
    if exemple.atr7 != None:
        exemples[exemple].atr7 = exemple.atr7
    if exemple.atr8 != None:
        exemples[exemple].atr8 = exemple.atr8
    if exemple.atr9 != None:
        exemples[exemple].atr9 = exemple.atr9
    if exemple.atr10 != None:
        exemples[exemple].atr10 = exemple.atr10

    return exemples[exemple]

I know that I can do this validation in another place, but I would still need to do the 10 ifs to validate each field. I'm looking for a way to just get the name of the each attribute and then loop with a for and validade in an if-statment inside the for-loop. I search for ways to do this. I found a way that uses __dict__, vars(MyClass) but I couldn't make it work. Can anyone help me to do this in a simple way?

CodePudding user response:

I can suggest two ways to do this and avoid these if else block.

First: You can use properties. For each attribute in your class, add a property (getter and setter). In this way every time new value is assigned to each attribute of your class from anywhere, you have your validation. (One important note you should keep in mind is that properties are slow in I/O tasks).

class MyClass:
    _atr1: float

    @property
    def atr1(self) -> float:
        """
        This is getter method and just return the
       _atr1 attribute.
        """
        return self._atr1

    @atr1.setter
    def atr1(self, value) -> None:
        """
        This is setter method for _atr1 and has a
        little validation. If validation will not pass
        It will raise an ValueError.
        """
        if value > 0:
            self._atr1 = value
            return
        raise ValueError("The value is not enough!")

Seconds: You can use descriptors. In this way you can add many descriptors with out side of your class, and assign this descriptors to your attributes. One important advantage of descriptors over properties is that you can use them in many classes and they have better speed.

class NumberDescriptor(object):
    """
    This class is a descriptor which validate the value
    to be greater than 0 
    """

    def __init__(self, number=None):
        self.number = number

    def __get__(self, obj, objtype):
        return self.number

    def __set__(self, obj, number):
        if number > 0:
            self.number = number
        else:
            raise ValueError("The value is not enough!")


class MyClass:
    atr1 = NumberDescriptor()
    atr2 = NumberDescriptor()        
    atr3 = NumberDescriptor()


c = MyClass()
c.atr1 = 0

ValueError: The value is not enough!
  • Related