-
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
refactor!: use the same conversion system for hybrids and columns #371
refactor!: use the same conversion system for hybrids and columns #371
Conversation
fix: insert missing create_type in union conversion Breaking Change: convert_sqlalchemy_type now uses a matcher function Breaking Change: convert_sqlalchemy type's column and registry arguments must now be keyword arguments Breaking Change: convert_sqlalchemy_type support for subtypes is dropped, each column type must be explicitly registered Breaking Change: The hybrid property default column type is no longer a string. If no matching column type was found, an exception will be raised. Signed-off-by: Erik Wrede <[email protected]>
def register(self, matcher_function: Callable[[Any], bool], func=None): | ||
if func is None: | ||
return lambda f: self.register(matcher_function, f) | ||
self.registry[matcher_function] = func | ||
return func |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to adapt this to functools' SingleDispatch to allow directive chaining
Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
Codecov ReportBase: 96.24% // Head: 96.34% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #371 +/- ##
==========================================
+ Coverage 96.24% 96.34% +0.09%
==========================================
Files 9 9
Lines 879 903 +24
==========================================
+ Hits 846 870 +24
Misses 33 33
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
chore: change the exception types Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice—much cleaner! looking forward to seeing the rest of the changes.
pass | ||
|
||
@hybrid_property | ||
def hybrid_prop(self) -> "MyTypeNotInRegistry": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth testing that this fails for a class type not in the registry as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, added that!
graphene_sqlalchemy/converter.py
Outdated
# ToDo Move to a separate converter later, matching all sqlalchemy mapped | ||
# types and returning a dynamic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
until this is done, there will still be the issue with initialization order, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I moved it to a function now but will change it to a dynamic later so we can recognize and wrap it in another dynamic in the filter PR - similar to how we do it with relationship filters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I realized we can't use Dynamic here, because graphene can't unpack nested dynamics (e.g. graphene.List(graphene.Dynamic)
)
So we'll need to check if a field type is a function (inspect.isfunction(_type) or isinstance(_type, partial)
) additionally to the instanceof dynamic
check in the filtering PR. This way we still maintain lazy support here.
registry: Registry = None, | ||
**kwargs, | ||
): | ||
# fixme drop the primary key processing from here in another pr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the preferred way of handling primary_keys?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#102 is relevant here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly what was mentioned in #102 and also the discussion we recently had about graphql-python/graphene#1428
Out of scope for this refactor though, just added the fixme so it's marked in the code
@convert_sqlalchemy_type.register(column_type_eq(float)) | ||
@convert_sqlalchemy_type.register(column_type_eq(sqa_types.Float)) | ||
@convert_sqlalchemy_type.register(column_type_eq(sqa_types.Numeric)) | ||
@convert_sqlalchemy_type.register(column_type_eq(sqa_types.BigInteger)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a bit off topic here, but wondering about the choice of BigInteger->Float. assume this would break down after 64 bits. any plans for graphql-core to provide a BigInt type like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, let's put this into an issue. Will have a look at it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Started #373
Signed-off-by: Erik Wrede <[email protected]>
Signed-off-by: Erik Wrede <[email protected]>
@sabard All fixed up, feel free to merge if nothing else comes up :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
I've had regression in my code after upgrading graphene-sqlalchemy and it just by reading the changes one by one that I saw these breaking changes. I'd like to suggest such breaking changes to be indicated in the release notes otherwise they are hard to find. |
@guillaumep sorry about that, it seems like the release notes for this release were not published properly. We will fix that up soon. Apart from that, how is this change working for you? We haven't received too much feedback yet, so I'd love to hear your thoughts. |
Don't worry about it, and thanks for listening to my feedback.
I was not completely aware of this convention in open source projects. I'll pay more attention to it starting now.
Great!
I upgraded from 3.0.0b3 to 3.0.0b4. Once I figured out how to change my code everything worked pretty well. We register a custom multi lingual dictionary SQLAlchemy type, which serializes data to a string in the database. We are providing the data as an object to the API clients. Here's the code diff for the upgrade: from graphene_sqlalchemy.converter import convert_sqlalchemy_type
+from graphene_sqlalchemy.utils import column_type_eq
-@convert_sqlalchemy_type.register(ModelMlText)
+@convert_sqlalchemy_type.register(column_type_eq(ModelMlText))
def convert_mltext_to_json(type, column, registry=None):
return MlText The key here was to figure-out the use of Thanks for the ongoing work on the project! |
This PR refactors the type conversion system to use the same converters for all column types (column + hybrid). Previously, we had
convert_sqlalchemy_column
usingsingledispatch
andconvert_sqlalchemy_hybrid_property_type
using a custom singledispatch-like solution to match column and hybrid property type(-hints) to their graphene equivalents. This implied having to maintain two separate type conversion systems with very similar functionality. This PR proposes merging the systems based on our custom singledispatch-style approach. This has several implications:Breaking Change: convert_sqlalchemy_type now uses a matcher function
Our custom matcher needs to be able to check more than just the type of a column. Some type hints like unions or optionals need further processing. To convert old singledispatch-style converters to the new converters, the column type has to be wrapped in our custom
value_equals
helper function:Breaking Change: convert_sqlalchemy_type support for subtypes is dropped, each column type must be explicitly registered
Since we now use matcher functions, it doesn't make sense to check if any inherited types match the singledispatch converters, as they might interfere with other matchers. Because of that, all types must now be explicitly registered:
See
converter.py
for more detailed examples.Breaking Change: The first argument to converter functions is always a type
Previously, both type and instances of types were passed to the converter. (
String
vsString(30)
). If you need information from the instance, usecolumn.type
instead. Remember to check ifcolumn
is present and not ahybrid_property
Example:
Breaking Change: convert_sqlalchemy_type's column and registry arguments must now be keyword arguments
To make additional support for type conversions possible, we converted all arguments to the converters to keyword arguments. It is not guaranteed for these to be present, so you should always check for existance of
column
orregistry
if you plan on using them.Since other arguments might be provided as well, make sure that
**kwargs
is present on your signature and that it is passed to any calls of nested type conversions (e.g.List[JSON]
callsconvert_list
, thenconvert_list
starts another conversion for the inner type)Breaking Change: The hybrid property default column type is no longer a string. If no matching column type was found, an exception will be raised.
We introduced warnings for the string fallback in
3.0.0b2
as a soft transition to the new system. Since we merged both conversion systems, it is impractical to have a fallback for all types. If you want to keep the oldstring
types for your hybrids, please use: