Home > database >  Any way to get all possible Python class variables, including ones without values?
Any way to get all possible Python class variables, including ones without values?

Time:06-08

I'm creating a class to represent a query, like this:

class Query:
    height: int
    weight: int
    age: int
    name: str
    is_alive: bool = True

As you can see, some variables start off initialized with defaults, others don't.

I want to implement chainable setters like so

    def of_height(self, height):
        self.height = height
        return self

    def with_name(self, name):
        self.name = name
        return self

    ...

The goal is to call this from several places in the project like so:

q = Query()
q.of_height(175).with_name("Alice")

Then I want to call a q.validate() that checks if any fields were not set, before calling an API with this query.

I can't figure out a way to dynamically check all possible variables, set or not, to check if any were left unset. Ideally, I don't want to implement a validate that has to be changed every time I add a possible query dimension in this class.

CodePudding user response:

The variable annotations collected during class body execution are stored in an __annotations__ attribute which you can use.

>>> Query.__annotations__
{'height': int, 'weight': int, 'age': int, 'name': str, 'is_alive': bool}

This is documented in the datamodel under the "Custom classes" section.

Usually, you would not access this attribute directly but use inspect.get_annotations instead, which provides a few conveniences.

CodePudding user response:

Following on @wim's solution, it would be desirable to get annotations from self so that a validate method will work with subclasses. Following is an implementation using inspect.get_annotations - but note that its a 3.10 feature.

#!/usr/bin/env python3.10

import inspect
import itertools

class Query:
    height: int
    weight: int
    age: int
    name: str
    is_alive: bool = True

class Query2(Query):
    foo: int

    def get_annotated_attrs(self):
        return set(itertools.chain.from_iterable(inspect.get_annotations(Q).keys() for Q in self.__class__.__mro__))

    def validate(self):
        for name in self.get_annotated_attrs():
            if not hasattr(self, name):
                return False
        return True

q2 = Query2()
print(q2.get_annotated_attrs())
print(q2.validate())

CodePudding user response:

I was thinking of something like this

import inspect

class Query:
    height: int
    weight: int
    age: int
    name: str
    is_alive: bool = True
    
    avilable_dimentions = ['height', 'weight', 'age', 'name', 'is_alive']

    def of_height(self, height):
        self.height = height
        return self

    def with_name(self, name):
        self.name = name
        return self
    
    def validate(self):
        not_defined = []
        for dim in self.avilable_dimentions:
            try:
                eval(f'self.{dim}')
            except:
                not_defined.append(dim)
        
        if not_defined:
            raise Exception(f'Missing dimentions {not_defined}') 
        return self

class Query2(Query):
    height2: int
    weight2: int

    avilable_dimentions = Query.avilable_dimentions   ['height2', 'weight2']
    

q = Query2()
q = q.of_height(175).with_name("Alice").validate()
  • Related