-
Notifications
You must be signed in to change notification settings - Fork 227
Discussion: General Mechanism to Selectively Override Automatic Field Creations #209
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
Comments
@jnak - thanks for starting that discussion. I like your proposed solution. Some thoughts from my side: The last example is a bit unclear - are you suggesting that fields are automatically whitelisted or that they can be by setting When you say I also suggest to provide something simpler than Btw, you always write SOAT instead of SAOT :) Seems to be a bad choice of acronym - had to fix the same typo in my own last comment, too. |
Thanks guys for the feedback. Good point regarding Regarding whitelisting what I meant is that # in a shared module
class BaseSQLAlchemyObjectType(graphene_sqlalchemy.SQLAlchemyObjectType):
class Meta(object):
abstract = True
@classmethod
def __init_subclass_with_meta__(cls, **kwargs):
super(OscarSQLAlchemyType, cls).__init_subclass_with_meta__(
only_fields=(),
**kwargs
)
...
# in a different module
class MyType(BaseSQLAlchemyObjectType):
class Meta:
model = MyModel
only_fields = (
'a_public_column',
...
) It does the job but it felt a little awkward to have sometimes long list of column names in class MyType(BaseSQLAlchemyObjectType)
class Meta:
model = MyModel
a_public_column = orm_field()
... That seems much more natural in my opinion. I also like that it decreases the "cost" of adding a description to a column / field - it's just a matter of adding a Regarding connection fields, I realize that my example was poorly chosen because # special_connection_factory is a factory that instantiates a user defined connection class
my_connection = orm_field(connection_factory=special_connection_factory) This is because there are only 2 types of connection fields: the default UnsortedSQLAlchemyConnectionField and a custom user-defined class. Given that we want to give the flexibility to override the connection class with any custom class, we need to have a parameter that takes a connection field factory (vs a boolean). I agree though that Anyway I'm not too concerned about this ugly name because it's quite an advanced feature. It's only for users that have defined their own connection class and need to override the SAOT connection behavior selectively because I'm not going to remove the def orm_field(special_connection=None, **kwargs):
if special_connection:
kwargs['connection_factory'] = special_connection_factory
return graphene_sqlalchemy.orm_field(**kwargs) Btw I'm going to be offline for a week. I'll address your feedback if there is any when I get back. |
Thanks for the additional info. The Regarding the |
Thanks for your feedback @Cito and @Nabellaleen. I've sent the first PR. I'm going to break up the work in multiple PRs because this one is already quite large. I'll do the enum overrides and the connection overrides in separate PRs as soon as this is one is approved. Cheers, |
…#214) This implements the overriding mechanism discussed in #209 . Changes include: - Add ORMField class - The main overridable parameters are: type, description, deprecation_reason and required - We can name fields differently using prop_name. This was preferred over name to avoid confusion / collision with graphene.Field parameters. - Add tests for all types of SQLAlchemy properties: columns, relationships, column properties, hybrid properties and and composite properties. - Cleanups and re-organize some tests.
Overview
In order to convert SQLAlchemy object models (SAM) into a full-blown GraphQL schema, SQLAlchemyObjectType (SAOT) does a lot of automatic mapping and conversions:
While it is convenient that SAOT does all this out-of-the-box, developers should ultimately be in control of the Graphene types generated. Currently we can override SAOT default's behavior in 2 ways.
1. Use
Meta
The
Meta
of SAOT allows the overriding of a few select automatic behaviors. For example, we can restrict what columns are exposed via theonly_fields
andexclude_fields
parameters. In this example,Meta
works fine because those parameters essentially act as toggles that completely disable or enable SOAM behavior for each field.Similarly, we can currently override SAOT default connection field factory via
connection_field_factory
. But this is not flexible enough because we may want to have SAOT use different factories for different fields. For example, it may make sense to have only one paginated field be sortable (hence useSQLAlchemyConnectionField
) and all the other fields be regular Relay-style collections (hence useUnsortedSQLAlchemyConnectionField
).One simple workaround would be to have a have a
connection_field_factories
parameter that takes a dictionary of field names to factories:While that works fine, it leads to the creation of many parameters in the
Meta
class (eg #178 and #208 (comment)) and we may end up with aMeta
class that becomes hard to maintain.My main concern though with this pattern is that scatters the logic for a field in separate
Meta
parameters. For example, we would need to do something like to rename a field, specify a connection factory and deprecate it:We need to look at multiple places just to understand how we are overriding the SAOT default for a given field. I much prefer Graphene's pattern of grouping all the logic for a field into a
Field
definition on theObjectType
class.2. Redefine the Graphene field
Since SAOT is a subclass of graphene
ObjectType
, we can always redefine the field auto-generated by SAOT. For example, we can change the type of a field like this:Here it works pretty well because SAOT does not do much to the
id
column. The only downside is that we lose the description automatically generated by SAOT based on the SQLAlchemy column.But the more logic SAOT does when converting a column to a field, the more we lose when we redefine a field from scratch. That's why I expect this pattern to become more of a problem as we improve SAOT. For example, one performance improvement that we probably want to do eventually is to only select the SAM columns for fields that have been queried. To do this, SAOT needs to build a mapping of columns to fields when it auto-generates fields. Regular Graphene fields (not generated by SAOT) will need to be manually annotated with the SAM fields they depend on. That's one more thing that we would lose" by redefining manually the Graphene field.
Proposed Solution
We could have a
sql_field
function that would look a lot like agraphene
Field
. The only difference is that instead of instantiating a field from scratch, it would use a combination of SOAT default and the user defined overrides:The main benefits of that approach are:
sql_field
callMeta
to set default values such asconnection_field_factory
One interesting side effect is that fields are automatically whitelisted:
From an API design perspective I can't think of any downside. Maybe a little more typing in some cases but I think we should optimize for clarity over the number of keystrokes.
What do you think of this pattern? Do you see any downsides? Do you have any ideas on how to make it better? I would love to hear your thoughts. I will send a PR if we agree this the right pattern to follow.
The text was updated successfully, but these errors were encountered: