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

Interoperability with other Python GraphQL libraries #34

Open
miracle2k opened this issue Nov 12, 2019 · 11 comments
Open

Interoperability with other Python GraphQL libraries #34

miracle2k opened this issue Nov 12, 2019 · 11 comments

Comments

@miracle2k
Copy link

miracle2k commented Nov 12, 2019

I have a fairly large GraphQL API written in graphene. I am interested in moving to ariadne, but I'd rather not migrate the whole thing in one go. So, since both are based on graphql-core (more or less, the @2 vs @3 thing is a bit of a pain, but solvable), and both ultimately generate a graphql-core schema, I wondered, why not write some schema types in ariadne, while keeping the rest in graphene for now? This allows a step-by-step migration.

It turns out this is pretty easy from the Graphene side:

class External():
    def __init__(self, type):
        super(External, self).__init__()
        self._type = type

class BetterGraphene(GrapheneGraphQLSchema):

    def __init__(
        self,        
        external_types=None,
        **kwargs
    ):
        self.external_types = external_types
        GrapheneGraphQLSchema.__init__(self, **kwargs)

    def type_map_reducer(self, map_, type_):
        if isinstance(type_, External):
            type_ = self.external_types[type_._type]
        return super().type_map_reducer(map_, type_)

    def get_field_type(self, map_, type_):
        if isinstance(type_, External):
            return self.external_types[type_._type]
        return super().get_field_type(map_, type_)

Now I can do:

class Foo(graphene.ObjectType):
    bar = Field(External("Bar"))

And I can create the Bar type however I want, and pass it to the graphene Schema() constructor. For example, I can create a schema with ariadne with make_executable_schema, and take the Bar type from the result.

This actually works!

The problem is that I can't link back. That is, if Bar (no defined with Ariadne) wants to refer to a type that is still in Graphene. Or, simple custom scalars like a DateTime - I can't have them defined in Graphene, and use them from an Ariadne type, and of course, they can't be defined twice.

Ordinarily I would say that I maybe I am just asking for too much: Making those two libraries interact. But as you can see by how little code is required on the Graphene side, this is actually pretty straightforward and without much complexity. Again, this is because the way graphql-core functions: It does the hard lifting, and we are just syntactic sugar on top.

And Graphene makes it super easy to change the mechanism of how the graphql-core schema is generated, by allowing a subclass to override methods.

In Ariadne, make_executable_schema is a bit of a blackbox. What would be needed to make this work is just a little room in make_executable_schema to allow to inject custom types; maybe allowing a custom resolve_type function to be passed through to the ASTDefinitionBuilder.

What are your thoughts?

@patrys
Copy link

patrys commented Nov 12, 2019

I was thinking about allowing GraphQLTypes to be passed as type defs to make_executable_schema. This would allow you to extract a type defined in one framework and use it in another, it would also allow code-first approach to be mixed with schema-first (which may be useful if parts of your schema come from an introspection protocol rather than from a file on disk).

@miracle2k
Copy link
Author

Playing with this some more. Another option might be allowing the user to pass in a custom
graphql_core.GraphQLSchema subclass. This would allow this kind of customization in a very similar way to I was doing for graphene above, and, I am sure, would come in handy as an escape hatch in all kinds of cases.

@miracle2k
Copy link
Author

miracle2k commented Nov 16, 2019

This might be more difficult than I thought. Not looking closely enough before, I failed to realize that most of the code, such as build_ast_schema is actually part of graphql-core, not Ariadne. I don't think there is the option of adding features to graphql-core, since they are a 1:1 part of graphql.js. And graphql.js apparently intends the buildASTSchema function to be a way to parse a fully complete schema.

So given these limitations, I can't see how make_executable_schema() in Ariadne can ever parse an incomplete schema definition, as long as it builds on top of build_ast_schema. Curious of @patrys had a particular solution in mind to support passing custom GraphQLTypes.

@rafalp
Copy link
Contributor

rafalp commented Nov 16, 2019

I am not a fan of shoving this logic into the make_executable_schema. Introducing separate utils providing inter-op would be much preferable here.

I think that simplest (even if not top-performing) approach to your problem would be to use some utility that renders SDL for Graphene type, and use that SDL as intermediate for combining your Graphene types with Ariadne ones. One could pair this with util that converts Graphene's types to Ariadne's bindables:

from ariadne import gql, make_executable_schema
from ariadne_graphene import make_graphene_schema_bindable
from graphene import print_schema
from mygraphene_api import graphene_schema


type_defs = gql("""
type Bar {
    name: String
}
""")

schema = make_executable_schema(
    [print_schema(graphene_schema), type_defs], 
    make_graphene_schema_bindable(graphene_schema)
)

However, migrating any larger API from Graphene to Ariadne appears to me as something that should be a process with list of steps one should take and a set of code utilities enabling developers to complete those steps before moving on to next one. Something like this propably:

  • Change query execution/server layer to Ariadne and use those utils to make it work with your graphene schema.
  • Incrementally port your Graphene object types to Ariadne, writing SDL for them and moving their resolvers to Ariadne bindables. Use extending feature to add new fields to not-yet ported types.
  • Port scalars over to Ariadne.
  • Drop Graphene from project.

@miracle2k
Copy link
Author

@rafalp Thanks for that idea. I can see that this is a way to do it, though I'm not quite sold on it's simplicity. In the meantime I did figure out a way to patch the schema returned by Ariadne to refer to the Graphene object types where appropriate, and using directives:

type_defs = gql("""
scalar External;
directive name on FIELD_DEFINITION;
type Bar {
    foo: External @name("GrapheneFoo")
}
""")

But implementing this turned out to be quite a bit of effort as well, so I'm not sure if the SDL-generating approach would have been more straightforward.

It still seems to me that there is a potential here - since all GraphQL libraries in Python just generate graphql-core objects, they should be able to interoperate nicely. But at this point I feel I am in over my head, and having solved my own problem, I'll let others decide if there is something actionable here.

@Sergei-Rudenkov
Copy link

make_graphene_schema_bindable

hi @rafalp! Could you provide approximate realization of make_graphene_schema_bindable from your response?

@rafalp
Copy link
Contributor

rafalp commented Jun 29, 2021

@Sergei-Rudenkov this is not possible currently. Ariadne is using graphql-core v3 while Graphene is still on v2, and both of those versions share same namespace.

@Sergei-Rudenkov
Copy link

@rafalp I use graphene==3.0.0b6 that use graphql-core v3 under the hood. So I basically have the same question as initial. How to start migration having some api using graphene and some api start using ariadne?

@rafalp
Copy link
Contributor

rafalp commented Jul 5, 2021

@Sergei-Rudenkov Sorry but no. I'm not able to put time now to dive into Graphene internals to develop thing like that.

@rafalp
Copy link
Contributor

rafalp commented Sep 16, 2022

I have an idea for changes to make_executable_schema that Ariadne GraphQL Modules implements that would make Graphene's v3 types work. There would be either a mixin or pass-through function that would make Graphene's type Ariadne-compatible.

@rafalp rafalp transferred this issue from mirumee/ariadne Jan 23, 2024
@rafalp
Copy link
Contributor

rafalp commented Jan 23, 2024

Next Ariadne GraphQL Modules is designed to support multiple approaches (APIs? frontends?) to type definition to solve this issue. But we would still need a Graphene-like API for types definitions to take care of this.

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

No branches or pull requests

4 participants