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()