-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhancements: SQLA Interface, Mutation, InputObjectType and auto schema generation #226
Comments
Lastly, a nice to have feature would auto generate the schema and query for you, given a list of models, interfaces, excluded models, and a default node interface (because we'll usually be overriding Node with a custom node that provides the database ID anyways) e.g. class Query(SQLAlchemyAutoSchemaFactory):
class Meta:
interfaces = (FooInterface, BarInterface,)
models = (*foos, *bars, *others, *associatives) # associatives are for AssociationObjectTypes, need to create the graphene models for these to work
node_interface = CustomNode
excluded_models = (TrackedEntityModel, BaseControlledVocabulary) class SQLAlchemyAutoSchemaFactory(graphene.ObjectType):
@staticmethod
def set_fields_and_attrs(klazz, node_model, field_dict):
_name = to_snake_case(node_model.__name__)
field_dict[f'all_{(pluralize_name(_name))}'] = FilteredConnectionField(node_model)
field_dict[_name] = node_model.Field()
# log.info(f'interface:{node_model.__name__}')
setattr(klazz, _name, node_model.Field())
setattr(klazz, "all_{}".format(pluralize_name(_name)), FilteredConnectionField(node_model))
@classmethod
def __init_subclass_with_meta__(
cls,
interfaces: Tuple[Type[SQLAlchemyInterface]] = (),
models: Tuple[Type[DeclarativeMeta]] = (),
excluded_models: Tuple[Type[DeclarativeMeta]] = (),
node_interface: Type[Node] = Node,
default_resolver: ResolveInfo = None,
_meta=None,
**options
):
if not _meta:
_meta = ObjectTypeOptions(cls)
fields = OrderedDict()
for interface in interfaces:
if issubclass(interface, SQLAlchemyInterface):
SQLAlchemyAutoSchemaFactory.set_fields_and_attrs(cls, interface, fields)
for model in excluded_models:
if model in models:
models = models[:models.index(model)] + models[models.index(model) + 1:]
possible_types = ()
for model in models:
model_name = model.__name__
_model_name = to_snake_case(model.__name__)
if hasattr(cls, _model_name):
continue
if hasattr(cls, "all_{}".format(pluralize_name(_model_name))):
continue
for iface in interfaces:
if issubclass(model, iface._meta.model):
model_interface = (iface,)
break
else:
model_interface = (CustomNode,)
_node_class = type(model_name,
(SQLAlchemyObjectType,),
{"Meta": {"model": model, "interfaces": model_interface, "only_fields": []}})
fields["all_{}".format(pluralize_name(_model_name))] = FilteredConnectionField(_node_class)
setattr(cls, "all_{}".format(pluralize_name(_model_name)), FilteredConnectionField(_node_class))
fields[_model_name] = node_interface.Field(_node_class)
setattr(cls, _model_name, node_interface.Field(_node_class))
possible_types += (_node_class,)
if _meta.fields:
_meta.fields.update(fields)
else:
_meta.fields = fields
_meta.schema_types = possible_types
super(SQLAlchemyAutoSchemaFactory, cls).__init_subclass_with_meta__(_meta=_meta, default_resolver=default_resolver, **options)
@classmethod
def resolve_with_filters(cls, info: ResolveInfo, model: Type[SQLAlchemyObjectType], **kwargs):
query = model.get_query(info)
for filter_name, filter_value in kwargs.items():
model_filter_column = getattr(model._meta.model, filter_name, None)
if not model_filter_column:
continue
if isinstance(filter_value, SQLAlchemyInputObjectType):
filter_model = filter_value.sqla_model
q = FilteredConnectionField.get_query(filter_model, info, sort=None, **kwargs)
# noinspection PyArgumentList
query = query.filter(model_filter_column == q.filter_by(**filter_value))
else:
query = query.filter(model_filter_column == filter_value)
return query |
Hello @maquino1985 , Thanks for starting these 4 enhancement proposals. There is quite a bit to unpack and discuss about each of those enhancements. Since those enhancements seem to be fairly independent from one another, can you create separate issues in order for the discussions to be productive and clear? For example, I agree with you that there is potential for Given that we have only a couple of semi-active maintainers for this project, I would also suggest that you start with the one issue you care the most about so we can make good progress on it instead making slower progress on all of those. There is only so much time per week the maintainers can dedicate to this project. I hope you understand. |
Hi @jnak , can you elaborate on your question about input validation? Other than requiring specific inputs of specific types in the Mutation Arguments, input validation should always occur in the SQLAlchemy model and/or at the database level upon updating or saving any data. I |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related topics referencing this issue. |
Currently there are at least 3 critical features missing from the library:
Interface support, Mutation support, and InputObjectType support that integrate with SQLAlchemy models. A last, but nice to have, would be a type that automatically generates all graphene-sqlalchemy models and correctly assigns them the appropriate Interface type based on their model.
Interface is critically important for people using polymorphic database structures. The importance of auto-generated SQLAlchemy Mutations and InputObjectTypes is self explanatory.
a SQLAlchemyInterface should have as its meta fields a name and an SQLAlchemy model, e.g.
Because it will act as a Node elsewhere, in my implementation I have it extend Node (but call super(AbstractNode) to specify it's meta rather than have it be overridden)
A mutation should take as its meta arguments the SQLAlchemy Model, it's CRUD operation . (Create Edit or Delete), and the graphene structure of its response (Output type)
an SQLAlchemy InputObjectType should introspect the sqla model and autogenerate fields to select based upon and set the appropriate field data type:
e.g.
The text was updated successfully, but these errors were encountered: