A very common and useful pattern when using frameworks is they force coupling between class attributes and methods to work properly.
An Example:
class LeadChoiceQueries(ObjectType):
industries = graphene.List(ChoiceType)
def resolve_industries(parent, info):
return [
ChoiceType(value, _) for value, _ in
IndustryCategory.CHOICES]
In this example we have an attribute industries
it's type is graphene List
and to finally put data in it, the framework relies on the resolve_industries
method.
Recently I had to write a lot of these resolvers
class LeadChoiceQueries(ObjectType):
industries = graphene.List(ChoiceType)
residence_types = graphene.List(ChoiceType)
organization_types = graphene.List(ChoiceType)
designations = graphene.List(ChoiceType)
education_levels = graphene.List(ChoiceType)
profession_categories = graphene.List(ChoiceType)
dependent_choices = graphene.List(ChoiceType)
marital_statuses = graphene.List(ChoiceType)
gender_choices = graphene.List(ChoiceType)
def resolve_industries(parent, info):
return [
ChoiceType(value, _) for value, _ in
IndustryCategory.CHOICES]
def resolve_residence_types(parent, info):
return [
ChoiceType(value, _) for value, _ in
ResidenceType.CHOICES]
def resolve_organization_types(parent, info):
return [
ChoiceType(value, _) for value, _ in
OrganizationType.CHOICES]
def resolve_designations(parent, info):
return [
ChoiceType(value, _) for value, _ in
Designation.CHOICES]
def resolve_education_levels(parent, info):
return [
ChoiceType(value, _) for value, _ in
EducationLevel.CHOICES]
def resolve_profession_categories(parent, info):
return [
ChoiceType(value, _) for value, _ in
ProfessionCategory.CHOICES]
def resolve_dependent_choices(parent, info):
return [
ChoiceType(value, _) for value, _ in
DependentChoices.CHOICES]
def resolve_marital_statuses(parent, info):
return [
ChoiceType(value, _) for value, _ in
MaritalStatus.CHOICES]
def resolve_gender_choices(parent, info):
return [
ChoiceType(value, _) for value, _ in
Gender.CHOICES]
As you can see, so many resolvers doing the same thing. So Sad!
If there was just some way to dynamically add these resolver methods to the class.
Python Meta-Programming to the rescue
class ChoiceQueryResolverAdder(type(ObjectType)):
def __new__(cls, name, bases, attr):
def resolve_choices(choices):
def resolve_choices_inner(parent, info):
return [ChoiceType(value, _) for value, _ in choices]
return resolve_choices_inner
new_attr = {}
for key in attr:
new_attr[key] = attr[key]
if hasattr(attr[key], "CHOICES"):
new_attr["resolve_" + key] = resolve_choices(attr[key].CHOICES)
new_attr[key] = graphene.List(ChoiceType)
return super(ChoiceQueryResolverAdder, cls).__new__(
cls, name, bases, new_attr)
class LeadChoiceQueries(ObjectType, metaclass=ChoiceQueryResolverAdder):
industries = IndustryCategory
residence_types = ResidenceType
organization_types = OrganizationType
designations = Designation
education_levels = EducationLevel
profession_categories = ProfessionCategory
dependent_choices = DependentChoices
marital_statuses = MaritalStatus
gender_choices = Gender
The key to it is ChoiceQueryResolverAdder
metaclass.
It iterates over the attributes of the class it will generate and checks if the attribute has a CHOICE
attribute and if it does, adds the resolver method using those choices.
And voila, 100 lines of code down to 35.
And it's super DRY
Sucesss!!
Top comments (0)