-
Notifications
You must be signed in to change notification settings - Fork 228
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 setting @hybrid_property's return type from the functions type annotations. #340
Support setting @hybrid_property's return type from the functions type annotations. #340
Conversation
Codecov Report
@@ Coverage Diff @@
## master #340 +/- ##
==========================================
+ Coverage 96.95% 97.23% +0.28%
==========================================
Files 9 9
Lines 623 686 +63
==========================================
+ Hits 604 667 +63
Misses 19 19
Continue to review full report at Codecov.
|
Thank you for the PR! I'm not done with the review yet, but it looks great so far 😄—only a few style and readability comments I've made. Other than that, it looks great so far. Still testing it a bit, but this is a helpful change. Good job, @flipbit03 @conao3. |
Thank you for the initial review! Answering your question: To thoroughly test the new type inferring capabilities of the class Reporter(Base):
...
@hybrid_property
def hybrid_prop_first_article(self) -> Article:
return self.articles[0] So now, since we specified that type in there, to build the GraphQL The default case: @singledispatchbymatchfunction
def convert_hybrid_property_return_type_inner(arg: Any):
existing_graphql_type = get_global_registry().get_type_for_model(arg)
if existing_graphql_type:
return existing_graphql_type
raise Exception(f"I don't know how to generate a GraphQL type out of a \"{arg}\" type") If you set an unsupported type signature (in the example above, a GraphQL object which doesn't exist yet), an exception is thrown. So if all you need is to return a String, we don't need to specify any type signature at all. Idea: If you think throwing an exception is not the ideal case there, we can revert the default behavior to only return a |
I also just thought about generating support for python |
I think this is the best way to do it. Falling back to
Thanks for the clarification! As I mentioned above, I haven't reviewed every file just yet, so I must have missed that. I think that's a critical test case. However, I still believe that it should be moved to separate models for the hybrid property tests, as mentioned in my Review:
If you have a different opinion on that, feel free to convince me otherwise. |
Sounds good! In this case, it should not be much work to include the Graphene Enums. Do you see a use case for that? |
Thank you for the suggestions. Agree on all of them. I am still not able to see the Review Comments you mentioned in the reply above. I might be doing something wrong 😓 but did you send off your review yet? I don't see any request for changes etc. To be done:
|
Sorry, I forgot to press finish review 👀 |
6f6c61c
… hybrid_property type inference thing so that we don't need to pepper unwanted imports and generations in other unrelated tests.
@erikwrede, I have implemented the changes suggested from conversations and the code review. Looking forward to getting this merged! Thanks in advance. |
Did you remove the enum support you just added? |
@erikwrede I tried to add the Enum support but it wasn't possible for me to compare the types properly. The references would never match since we don't have the same Enum registered somewhere (even if you recreate it from a python enum again, they don't match with Idea: Would it be acceptable to compare the Enum in tests by just comparing the invariants? If you agree, then I might try to do it that way. I am trying to introduce self referential SQLAlchemy model properties and I think I might have a solution. |
Of course, you can try if you want. But we can also move that to a separate PR/Issue, so we don't block the changes here.
Are you referring to recursive types via hybrid properties? |
The issue is more in Graphene itself regarding its Enum generation 😢 graphene.Enum.from_enum(PYTHONENUM) always generates a new object, even if you pass it the same Python Enum. So the I just got self referential SQLAlchemy types working via class ShoppingCart(Base):
...
@hybrid_property
def hybrid_prop_self_referential(self) -> 'ShoppingCart':
return ShoppingCart(id=1)
@hybrid_property
def hybrid_prop_self_referential_list(self) -> List['ShoppingCart']:
return [ShoppingCart(id=1)] I used the fact that Graphene allows you to pass a Callable instead of a type and it'll call that callable later in the future. This is how it's implemented. @convert_sqlalchemy_hybrid_property_type.register(safe_isinstance(typing.ForwardRef))
def convert_sqlalchemy_hybrid_property_forwardref(arg):
"""
Generate a lambda that will resolve the type at runtime
This takes care of self-references
"""
def forward_reference_solver():
model = registry_sqlalchemy_model_from_str(arg.__forward_arg__)
# Always fall back to string if no ForwardRef type found.
return get_global_registry().get_type_for_model(model) or String
return forward_reference_solver I also needed a way to convert bare typing strings into ForwardRefs (things like @convert_sqlalchemy_hybrid_property_type.register(safe_isinstance(str))
def convert_sqlalchemy_hybrid_property_forwardref(arg):
"""
Convert Bare String to a ForwardRef
"""
return convert_sqlalchemy_hybrid_property_type(typing.ForwardRef(arg)) |
Looks good! Speaking of enums, there might have been issues on the graphene repo about that. I'll have a look later. If not, it might make sense to open an issue for enhancement there. |
I'm testing right now If we are covered in the EDIT: Seems to be working fine. Writing tests. |
Great. I think this would be a good point to consider basic feature integration done. enums are a different discussion. Maybe we should move that to a different issue/PR to keep readability? This discussion has gotten quite long and we agreed on new features in the middle. |
Absolutely agree that it should be another feature/discussion. I am submitting my PR in its current state for your review/approval. Thank you for your time. PS: Do you hang around in some Discord server or something? |
… `A` -> `B` -> `A` being generated via `@hybrid_props`)
@erikwrede I cannot see the button to 'resubmit for review' in Github, since it's still waiting for your (last) review (sorry), so I'm tagging you here. It is ready to be reviewed now and I feel satisfied with the level of support we achieved. Thank you! |
Thanks! I'll review it later. |
This PR adds support for generating the GraphQL types from a
@hybrid_property
based on the actual type hints set in the function's return type annotation.Based on this feedback comment, I devised a simple way to dispatch the actual final type generation machinery to a function using a mechanism similar to standard library's
@singledispatch
, but based on function matchers instead of fixed argument types. This way, we support most basic types in the standard library out of the box, and also give the end user the ability to extend the matcher system by registering new matchers in their own systems.Types signatures supported:
str
,int
,float
,bool
,datetime.date
,datetime.time
,datetime.datetime
,decimal.Decimal
,List[T]
whereT
is all of the above, and also any other previously generatedSQLAlchemyObjectType
and also self-referential
T where T: SQLAlchemy model
(usingForwardRef
string-like references).特別に、I'd like to thank @conao3 for the initial work this PR is based on. ありがとう!
closes #333