Skip to content
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

Support GQL interfaces for polymorphic SQLA models #365

Merged
merged 9 commits into from
Nov 28, 2022
Merged

Support GQL interfaces for polymorphic SQLA models #365

merged 9 commits into from
Nov 28, 2022

Conversation

polgfred
Copy link
Contributor

Hi!

Would love any feedback on this.

My background assumption here is that, for most folks using graphene_sqlalchemy bindings, GraphQL interfaces are going to take the form of polymorphic base classes. So it would be great if you could extend SQLAlchemyInterface the same way you extend SQLAlchemyObjectType for concrete types -- and wire up the attributes using the same reflection mechanism.

So, in order to accomplish this, I:

  • moved the logic in SQLAlchemyObjectType up into a SQLAlchemyBase type
  • made SQLAlchemyObjectType and (a new) SQLAlchemyInterface extend SQLAlchemyBase
  • updated Registry to accept objects of type SQLAlchemyBase

I've tested this in a work project that depends heavily on a few polymorphic JTI base types, and this mechanism works perfectly. However, I'm sure I haven't thought through potential limitations and incorrect assumptions, which is why I'm submitting this for review and feedback.

Thanks!

flipbit03
flipbit03 previously approved these changes Oct 24, 2022
Copy link
Contributor

@flipbit03 flipbit03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@erikwrede
Copy link
Member

Thanks for the PR! Will check it out this week 🙂

Copy link
Member

@erikwrede erikwrede left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, this looks great!

Can you please add a usage example, unit tests and some docstrings to give some guidance on when and how to use the new Base and Interface types?

@codecov
Copy link

codecov bot commented Nov 4, 2022

Codecov Report

Base: 96.97% // Head: 97.09% // Increases project coverage by +0.11% 🎉

Coverage data is based on head (1ed9dbe) compared to base (8bfa1e9).
Patch coverage: 100.00% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #365      +/-   ##
==========================================
+ Coverage   96.97%   97.09%   +0.11%     
==========================================
  Files           7        7              
  Lines         694      722      +28     
==========================================
+ Hits          673      701      +28     
  Misses         21       21              
Impacted Files Coverage Δ
graphene_sqlalchemy/registry.py 100.00% <100.00%> (ø)
graphene_sqlalchemy/types.py 95.39% <100.00%> (+1.03%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@polgfred
Copy link
Contributor Author

Can you please add a usage example, unit tests and some docstrings to give some guidance on when and how to use the new Base and Interface types?

@erikwrede Thanks! And sorry for the delay, been busy over here. I've added a doc, unit tests, and a docstring for ObjectType and Interface. Let me know if this is what you're looking for, and please feel free to tweak if desired.

@polgfred polgfred requested a review from erikwrede November 27, 2022 19:25
@erikwrede
Copy link
Member

erikwrede commented Nov 27, 2022

@polgfred thanks for the update! Appreciate the weekend-effort 🙂

There's two things we should discuss before merging this:

1. Should we explicitly exclude the field linked in polymorphic_on (in this case type) from the GQL schema?
The type field is verbose as GraphQL already provides the __typename field including the GQL type name. API-Design wise the type field does not bring any value as it is only relevant for server internals.
We could enable overriding the behavior using the ORMField: if an ORMField was created on the base/explicit type for the field containing the polymorphic identity, the field would not be excluded.
I think this leads to better schema design while still giving users the option to customize.

2. Removing polymorphic_identity: "person" from the examples
PersonType is an Interface in this case, so pure instances of PersonType are not allowed. However, the underlying DB Model allows pure instances of our base class Person because it provides a polymorphic_identity. This can lead to the following error if a pure Person instance is returned from the resolvers:

 "message": "Abstract type 'PersonType' must resolve to an Object type at runtime for field 'Query.people'. Either the 'PersonType' type should provide a 'resolve_type' function or each possible type should provide an 'is_type_of' function."

If the polymorphic_identity is not set for Person, SQLAlchemy will not allow such inserts.

We need to clarify that the Interface type is only suitable for models that have an abstract (maybe there's better wording) base that is guaranteed to not appear in the records.
Same goes for multi-table inhertance with a distinct table for the base (which is an uncommon case but it might still be used in existing designs).

Please let me know what you think!

@polgfred
Copy link
Contributor Author

@erikwrede Yeah, I agree with both those observations. An interface isn't exactly a polymorphic type for the reason you mentioned, but in my experience it's much more common for a polymorphic base class to be abstract, so we should call that out in the docs and examples. And I agree that it makes sense to hide the polymorphic_on field in the schema. I'll look into that tonight :)

@polgfred
Copy link
Contributor Author

@erikwrede Added checks for the stuff you called out.

erikwrede and others added 2 commits November 28, 2022 09:40
- Added a note that polymorphic_on fields are excluded by default.
- Added a note that a polymorphic_identity is unsupported for interfaces
…n-abstract Interface models

chore: added type name to assertion error for easier debugging during development
Copy link
Member

@erikwrede erikwrede left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now! Performed slight modifications to docs & the assertion error in SQLAlchemyInterface & added 3 unit tests. LMKWYT 🙂

@polgfred
Copy link
Contributor Author

Awesome, looks great to me!

@erikwrede erikwrede merged commit 2edeae9 into graphql-python:master Nov 28, 2022
Comment on lines +459 to +466
# make sure that the model doesn't have a polymorphic_identity defined
if hasattr(_meta.model, "__mapper__"):
polymorphic_identity = _meta.model.__mapper__.polymorphic_identity
assert (
polymorphic_identity is None
), '{}: An interface cannot map to a concrete type (polymorphic_identity is "{}")'.format(
cls.__name__, polymorphic_identity
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@polgfred I just realized that this might have been a mistake to add. Sometimes we have a base type that is extended by other types but has a polymorphic identity. In this case, there would be an object type implementing the interface with no additional fields. We might need to revert hat bit, what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants