Reputation: 6353
I currently have the following mutation defined for my project:
My class PlanetAttribute
is used to define Graphene Fields used as inputs of my mutations
class PlanetAttribute:
name = graphene.String(required=True, description="Name of the planet.")
rotation_period = graphene.String(default_value="unknown", description="Rotation period of the planet.")
orbital_period = graphene.String(default_value="unknown", description="Orbital period of the planet.")
diameter = graphene.String(default_value="unknown", description="Diameter of the planet.")
climate = graphene.String(default_value="unknown", description="Climate period of the planet.")
gravity = graphene.String(default_value="unknown", description="Gravity of the planet.")
terrain = graphene.String(default_value="unknown", description="Terrain of the planet.")
surface_water = graphene.String(default_value="unknown", description="Surface water of the planet.")
population = graphene.String(default_value="unknown", description="Population of the planet.")
url = graphene.String(default_value="unknown", description="URL of the planet in the Star Wars API.")
My class CreatePlanetInput
is used to define the Graphene input object type. Note that it inherits its attributes from the PlanetAttribute
class defined above.
class CreatePlanetInput(graphene.InputObjectType, PlanetAttribute):
"""Arguments to create a planet."""
pass
My class CreatePlanet
is my Graphene mutation class which takes the CreatePlanetInput
class as arguments.
class CreatePlanet(graphene.Mutation):
"""Create a planet."""
planet = graphene.Field(lambda: Planet, description="Planet created by this mutation.")
class Arguments:
input = CreatePlanetInput(required=True)
def mutate(self, info, input):
data = utils.input_to_dictionary(input)
data['created'] = datetime.utcnow()
data['edited'] = datetime.utcnow()
planet = ModelPlanet(**data)
db_session.add(planet)
db_session.commit()
return CreatePlanet(planet=planet)
Instead of declaring mutation inputs manually in the PlanetAttribute
class, I would rather generate them dynamically from my SQLALchemy class ModelPlanet
which is defined as below:
class ModelPlanet(Base):
"""Planet model."""
__tablename__ = 'planet'
id = Column('id', Integer, primary_key=True, doc="Id of the person.")
name = Column('name', String, doc="Name of the planet.")
rotation_period = Column('rotation_period', String, doc="Rotation period of the planet.")
orbital_period = Column('orbital_period', String, doc="Orbital period of the planet.")
diameter = Column('diameter', String, doc="Diameter of the planet.")
climate = Column('climate', String, doc="Climate period of the planet.")
gravity = Column('gravity', String, doc="Gravity of the planet.")
terrain = Column('terrain', String, doc="Terrain of the planet.")
surface_water = Column('surface_water', String, doc="Surface water of the planet.")
population = Column('population', String, doc="Population of the planet.")
created = Column('created', String, doc="Record created date.")
edited = Column('edited', String, doc="Record last updated date.")
url = Column('url', String, doc="URL of the planet in the Star Wars API.")
peopleList = relationship(ModelPeople, backref='planet')
How would you proceed?
Note that I have also posted the question here: https://github.com/graphql-python/graphene-sqlalchemy/issues/112
Upvotes: 3
Views: 1746
Reputation: 1567
I solved creating this class:
from graphene.types.utils import yank_fields_from_attrs
from graphene.utils.subclass_with_meta import SubclassWithMeta_Meta
from graphene_sqlalchemy.registry import get_global_registry
from graphene_sqlalchemy.types import construct_fields
class SQLAlchemyInputObjectType(graphene.InputObjectType):
@classmethod
def __init_subclass_with_meta__( # pylint: disable=arguments-differ
cls, model=None, registry=None, only_fields=(), exclude_fields=(),
optional_fields=(), **options
):
if not registry:
registry = get_global_registry()
sqla_fields = yank_fields_from_attrs(
construct_fields(model, registry, only_fields, exclude_fields),
_as=graphene.Field,
)
for key, value in sqla_fields.items():
if key in optional_fields:
type_ = value.type if isinstance(
value.type, SubclassWithMeta_Meta) else value.type.of_type
value = type_(
description=value.description
)
setattr(cls, key, value)
super(SQLAlchemyInputObjectType, cls).__init_subclass_with_meta__(
**options
)
You can use it with:
class CreatePlanetInput(SQLAlchemyInputObjectType):
class Meta:
model = ModelPlanet
exclude_fields = ('id', )
I tried to make it behave similar with SQLAlchemyObjectType
, so only_fields
and exclude_fields
should work as expected.
I also added an optional_fields
that makes it not required, good for an update mutation.
It does handle the simple cases, like scalar fields. Relationships, like peopleList
, you need have to declare manually.
Upvotes: 5