graphene-sqlalchemy icon indicating copy to clipboard operation
graphene-sqlalchemy copied to clipboard

Generate Input Arguments from SQLAlchemy Class?

Open alexisrolland opened this issue 7 years ago • 3 comments

Hello, Do you know if it's possible to generate input arguments dynamically from the SQLAlchemy class that will be transformed by the mutation? Example:

My input arguments for a CreatePerson mutation look like this:

class CreatePersonInput(graphene.InputObjectType):
    """Arguments to create a person."""
    name = graphene.String(required=True, description="Name of the person to be created.")
    height = graphene.String(default_value="unknown", description="Height of the person to be created.")
    mass = graphene.String(default_value="unknown", description="Mass of the person to be created.")
    hair_color = graphene.String(default_value="unknown", description="Hair color of the person to be created.")
    skin_color = graphene.String(default_value="unknown", description="Skin color of the person to be created.")
    eye_color = graphene.String(default_value="unknown", description="Eye color of the person to be created.")
    birth_year = graphene.String(default_value="unknown", description="Birth year of the person to be created.")
    gender = graphene.String(default_value="unknown", description="Gender of the person to be created.")
    planet_id = graphene.ID(default_value="unknown", description="Global Id of the planet from which the person to be created comes from.")
    url = graphene.String(default_value="unknown", description="URL of the person in the Star Wars API.")


class CreatePerson(graphene.Mutation):
    """Mutation to create a person."""
    person = graphene.Field(lambda: People, description="Person created by this mutation.")

    class Arguments:
        input = CreatePersonInput(required=True)

...

In the meantime, the input arguments for my UpdatePerson mutation look like this:

class UpdatePersonInput(graphene.InputObjectType):
    """Arguments to update a person."""
    id = graphene.ID(required=True)
    name = graphene.String()
    height = graphene.String()
    mass = graphene.String()
    hair_color = graphene.String()
    skin_color = graphene.String()
    eye_color = graphene.String()
    birth_year = graphene.String()
    gender = graphene.String()
    planet_id = graphene.ID()
    url = graphene.String()


class UpdatePerson(graphene.Mutation):
    """Update a person."""
    person = graphene.Field(lambda: People, description="Person updated by this mutation.")

    class Arguments:
        input = UpdatePersonInput(required=True)

...

Finally, my SQLAlchemy class look like this:

class ModelPeople(Base):
    """People model."""

    __tablename__ = 'people'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String)
    height = Column('height', String)
    mass = Column('mass', String)
    hair_color = Column('hair_color', String)
    skin_color = Column('skin_color', String)
    eye_color = Column('eye_color', String)
    birth_year = Column('birth_year', String)
    gender = Column('gender', String)
    planet_id = Column('planet_id', Integer, ForeignKey('planet.id'))
    created = Column('created', String)
    edited = Column('edited', String)
    url = Column('url', String)

...

This is all pretty redundant and it would be ideal if we could just reuse the SQLAlchemy class attributes in the InputObjectType

alexisrolland avatar Feb 09 '18 05:02 alexisrolland

I was able to factorize my code a little bit by creating a new class where I define attributes description only once. Then I supply this new class as a parent class of the following classes:

  • class People(SQLAlchemyObjectType, PeopleAttribute) : used for GraphQL queries
  • class CreatePersonInput(graphene.InputObjectType, PeopleAttribute) : used to define inputs for GraphQL create mutation
  • class UpdatePersonInput(graphene.InputObjectType, PeopleAttribute) : used to define inputs for GraphQL create mutation

Here is the code:

# Create a generic class to mutualize description of people attributes for both queries and mutations
class PeopleAttribute:
    name = graphene.String(required=True, description="Name of the person.")
    height = graphene.String(default_value="unknown", description="Height of the person.")
    mass = graphene.String(default_value="unknown", description="Mass of the person.")
    hair_color = graphene.String(default_value="unknown", description="Hair color of the person.")
    skin_color = graphene.String(default_value="unknown", description="Skin color of the person.")
    eye_color = graphene.String(default_value="unknown", description="Eye color of the person.")
    birth_year = graphene.String(default_value="unknown", description="Birth year of the person.")
    gender = graphene.String(default_value="unknown", description="Gender of the person.")
    planet_id = graphene.ID(default_value="unknown", description="Global Id of the planet from which the person comes from.")
    url = graphene.String(default_value="unknown", description="URL of the person in the Star Wars API.")


class People(SQLAlchemyObjectType, PeopleAttribute):
    """People node."""

    class Meta:
        model = ModelPeople
        interfaces = (graphene.relay.Node,)


class CreatePersonInput(graphene.InputObjectType, PeopleAttribute):
    """Arguments to create a person."""
    pass


class UpdatePersonInput(graphene.InputObjectType, PeopleAttribute):
    """Arguments to update a person."""
    id = graphene.ID(required=True, description="Global Id of the person.")

alexisrolland avatar Feb 10 '18 09:02 alexisrolland

Does anyone has a better way of doing this? I have also posted the question here : https://stackoverflow.com/questions/48806710/generate-graphene-mutation-inputs-from-sqlalchemy-class-attributes

alexisrolland avatar Feb 15 '18 10:02 alexisrolland

See this also: https://github.com/graphql-python/graphene-sqlalchemy/issues/29

arlobryer avatar Apr 24 '18 18:04 arlobryer