Code Monkey home page Code Monkey logo

graphene-federation's People

Contributors

andriipidlisnyi avatar chinskiy avatar dependabot[bot] avatar erebus1 avatar jonnii avatar noelleleigh avatar sakhatskyi avatar salwator avatar saultigh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphene-federation's Issues

Congrats on the work

Hi everyone,

I'm Syrus, creator of Graphene :). Great work on this package.
I would love to have the project under the umbrella of the graphql-python organization.

Let me know if you would be interested in forming part of the organization!

Regular expression to mark external fields is not working properly when defining filters using a field with the same name

As you can see in this example when printing the annotated schema in _mark_field method:

  • the external decorator is placed correctly here:
type User  @key(fields: "id") {
  id: ID! @external
  files(before: String, after: String, first: Int, last: Int): FileConnection
}  
  • as soon as I add a filter on the files id in the connection the regular expression gets confused and place the decorator in the wrong place:
type User  @key(fields: "id") {
  id: ID!
  files(before: String, after: String, first: Int, last: Int, id: ID): @external FileConnection
}  

Add support for descriptions

The graphql type descriptions get lost when I use graphene-federation.
The discription is present in the GraphiQL from the graphql microservice but not in the Graphql Playground of the apollo gateway.
If I implement the Graphql microservice with the apollo server everything works fine.

Unable to define _entities representations directly in the query

The problem is when you define _entities representations directly in the query, you'll receive the error:

query {
    _entities(representations: [{__typename: "File", id: "1"}]) {
        ... on File {
            id
            name
        }
    }
}
File ".../site-packages/graphene_federation/entity.py", line 34, in resolve_entities
    model = custom_entities[representation["__typename"]]
graphql.error.located_error.GraphQLLocatedError: 'ObjectValue' object is not subscriptable

The only way to use it is to define representations in the variables (https://github.com/preply/graphene-federation/blob/master/examples/entities.py)

ObjectValue is created from the parsed request by graphql-core and the representation looks like this:

ObjectValue(fields=[ObjectField(name=Name(value='__typename'), value=StringValue(value='File')), ObjectField(name=Name(value='id'), value=StringValue(value='1'))])

Type Query must define one or more fields

Hi there,

I'm currently trying to use the build_schema function for my graphene schema but for some reason, I get the error:
This data graph is missing a valid configuration. Type Query must define one or more fields.

Building the schema:

class FlowQuery(BaseQuery, SubscriptionQueries, flowQueries.Query, analyticsQueries.Query):
    """Global queries for application config"""

    pass


if settings.SERVICE == "flow":
    # SCHEMA = graphene.Schema(query=FlowQuery, mutation=FlowMutation)
    SCHEMA = build_schema(FlowQuery, FlowMutation)
else:
    # SCHEMA = graphene.Schema(query=MCQuery, mutation=MCMutation)
    SCHEMA = build_schema(MCQuery, MCMutation)

The commented out portion is how I was building my schema previously before. Both methods still run and I'm able to view my queries in graphql playground. However, for the federated schema, apollo gateway says there's something wrong. I'm not sure what it means when it says it must define one or more fields because I thought that the build_schema function did add a field

Would appreciate any insight on this. Not sure if I may have missed anything. Thank you!

New release

Hello. When do you plan to release a new version of this package? There have been several fixes since the last release and it would be great to see a new version on PyPI.

proposal: _entities resolver

Hello, here is an use case: I as a developer writing yet another service which only purpose is to extend already existing type in federated graph, e.g. my schema is:

extend type File @key(fields:"id") {
  id: ID! @external
  myAwesomeField: String
}

To test my service I gonna need to query account somehow, so I forced to add "technical" query resolver for this, e.g.:

extend type Query {
  _myTemporaryFileResolverForTesting(id: ID!): File
}

With that I am able to make queries in graphiql to test my service without being dependant on another services

Proposal: instead of writing this technical query resolvers I might and probably should use build in _entities resolver which is used by gateway, query will be something like:

{
  _entities(representations:[{__typename:"File",id:"1"}]) {
    ... on File {
      id
      name
    }
  }
}

But unfortunately it does not work and complains that 'ObjectValue' object is not subscriptable

So while query is passed from graphiql somehow we are working with ObjectValues rather than expected dictionary like objects which might be workarounded with addition to entity.py like:

if type(representation) is ObjectValue:
  dictified = dict()
  for field in representation.fields:
    dictified[field.name.value] = field.value.value
  representation = dictified

right before model = custom_entities[representation["__typename"]]

This change should be safe because we are not touching behaviour and will make lives easier

image

PS: it was early morning, meanwhile there is an workaround, you actually can call _entities resolver but it must be in following form

image

Issue when the type contains "implements" instruction

The issue is the next one: if your type contains an implements, like the ones generated by graphene-django, the library _sdl generation appends the @key("id") suffix before the implements, when it should be present afterwards.

This is caused by the service.py, line 29

for entity_name, entity in custom_entities.items():
        type_def = "type %s" % entity_name
        repl_str = "%s %s " % (type_def, entity._sdl)
        pattern = re.compile(type_def)
        string_schema = pattern.sub(repl_str, string_schema)

As we see on the type_def var, it does not consider the fact that there might be an "implement" or other keywords after the type name.

The solution is quite simple with a regex

Graphene v3 support

Is work being done on this package to make it compatible with Graphene v3 (release notes)? This update brings in a lot of useful bug fixes, and having the entire Graphene ecosystem ready to go when it's released would make a lot of developer lives easier.

I've been working on making the necessary changes myself, and I'm willing to put up a PR with my fixes. Is that something this project would welcome?

@key directive not present in built schema

I have an entity defined as follows:

@key(fields="id")
class MyEntity(graphene.ObjectType):
    id = graphene.ID(required=True)
    name = graphene.String(required=True)

    class Meta:
        interfaces = (graphene.relay.Node,)

    def __resolve_reference(self, info, **kwargs):
        return self.id

class Query(graphene.ObjectType):
    get_entity = graphene.Field(MyEntity, id=graphene.ID(required=True))

When I use the build_schema tool this is what is output

type MyEntity implements Node {
  id: Int!
  name: String!
}

without the required @key directive. Please advise on how I can get this to output as follows:

type MyEntity @key(fields: "id") {
  id: ID!
  name: String!
}

Federated services Database design

Hi,
It is not an issue but a question about the way of working.

I would like to be sure how should be designed the databases of different Federated services that will communicate to each other.

We have 2 graphql services and 2 different databases :

  • user
  • post

User service will contain a database with the following table

user table contains:
- Id
- email
- password

Post service will contain a database with the following table

Post table contains :
- id_post
- title
- description
- id_user

In Django :
User Model :

class User(models.Model):
    email = models.EmailField(_('email address'), unique=True)
    password = models.CharField(max_length=100)

Post Model :

class Post(models.Model):
	title = models.CharField(max_length=100)
	description = models.CharField(max_length=100)
	id_user = models.IntegerField()    
         // this field will get the id of the user from the other service

In graphql we would have

User service:

Type user key(fields=Id) {
     Id: int
     Email:string
     Password : string
}

Post service

Type post key(fields=Id) {
     Id: int
     Title : string
     Description : string
     user_id : user
}

Extend type user{
     Id: external
}

Is the translation from database to graphql or graphql to database is correct?

Am I right we the above design?

Does Each service that need to communicate to each other will have a reference to the other service database?

Will I have to manually get the user_id from the User service to insert it in the Post service?
Do I have to create a custom resolver to manage the foreign key from one service to another?

Do you have a demo that works with a database?

Thank you for your answers

graphene-federation 1.0.0 does not work with graphene v3.0.0b7

Currently I am using graphne v3.0.0b7 with graphene-federation 1.0.0. When I try to build project it throws following error

Because graphene-federation (==0.1.0) depends on graphene (>=2.1.0,<3)
  and my_project depends on graphene (==v3.0.0b7), graphene-federation is forbidden.
 So, because my_project depends on graphene-federation (==0.1.0), version solving failed.

I can change version of graphene and fix it but why is this error coming?

Is this repository dead?

Seems that there is no activity since the initial release. Should I consider this project dead? Is there a plan to find new contributors?

Can we get some verbose guidance on how to use provides?

Hi, can we get some more verbose guidance on how to use provides?
Given the following schemas

# origin ServiceA defines SchemaA
@key(fields="id")
class SchemaA(graphene.ObjectType):
  id = graphene.ID(required=True)
  a = graphene.String
  b = graphene.String
  
  class Meta:
    interfaces = (graphene.relay.Node,)

class Query(ObjectType):
  schema_a = graphene.relay.Node.Field(SchemaA)


# other service ServiceB extends SchemaA
@extends(fields="id")
class SchemaA(graphene.ObjectType):
  id = external(graphene.ID(required=True))
  b = external(graphene.String)
  c = graphene.String

  class Meta:
    interfaces = (graphene.relay.Node,)

class Query(ObjectType):
  schema_a = provides(graphene.relay.Node.Field(SchemaA), fields="b")

Is this how we provide field b in SchemaA in ServiceB?

Passing requiers field as an input to another resolver

Hey lets say i have 2 services
the first class looks like

@key(fields="id")
@key(fields="flowId")
class SubscriptionManager(graphene.ObjectType):
    id = graphene.String(required=True)
    flow_id = graphene.String()

    @staticmethod
    def resolve_flow_id(parent, info):
        return "ABC-123"

the in the second service i want to be able to pass the flow_id as an input to another resolver

@extend(fields="id")
class SubscriptionManager(graphene.ObjectType):
    id = external(graphene.String(required=True))
    flow_id = external(graphene.String())
    get_alot_of_params = requires(graphene.String(), fields="flowId")

i would like that get_alot_of_params will receive flow_id from the first service but also other params passed from the parent. is there a way to do it?

Support for multiple keys

Apollo Federation supports multiple primary keys. We could implement this in graphene-federation in one of two ways:

# Multiple key decorators
@key("id")
@key("username")
class Person(ObjectType):
    pass

# multiple keys in a single decorator
@key("id", "username")
class Person(ObjectType):
    pass

Can you let me know if you have a preference and I'll send a PR.

Error: Directive "external" may not be used on ARGUMENT_DEFINITION.

Hey, I just upgraded my project to a new version. I was using version 0.0.2 and everything was working fine. By upgrading to version >=0.0.4. I got the following errors in my Apollo gateway:

(node:3328) UnhandledPromiseRejectionWarning: GraphQLSchemaValidationError: Directive "external" may not be used on ARGUMENT_DEFINITION.

Directive "external" may not be used on ARGUMENT_DEFINITION.

[doc] ExamResult.id -> Found extraneous @external directive. @external cannot be used on base types.

[payment] Plan.id -> Found extraneous @external directive. @external cannot be used on base types.

Directive "external" may not be used on ARGUMENT_DEFINITION.

Both of my types are not using any external arguments.

Do you have any ideas on this?

Here is a working example with Mongoengine

Hello!

Thank you so much for creating this awesome tool. We are currently working with GraphQL + Mongoengine. We split up into Microservices, so Federation was the thing to do. After fiddling around for a day, I was able to create a working example with Mongoengine. I want to share it, just in case somebody needs it too.

1. Overview

The example is made up of two Services. Each Service has its own DB. We want to be able to connect these to DBs, but have access via 1 GraphQL Endpoint to be able to connect multiple Frontend Applications. I choose the following file-structure:

gateway
  |-gateway.js
graphene_federation
services
  |-models
    |-ReviewModel.py
    |-UserModel.py
  |-service1
    |-app.py
    |-config.py
    |-schema.py
    |-server.py
  |-service2
    |-app.py
    |-config.py
    |-schema.py
    |-server.py

2. Models

We have two DB-Models, a Userand a Review. Each of the models is sitting in its own Database. They are only connected via Mongoengine.

models/ReviewModel.py
from mongoengine import Document
from mongoengine.fields import StringField, ReferenceField

from .UserModel import UserModel


class ReviewModel(Document):
    name = StringField()
    user = ReferenceField(UserModel)
    meta = {'db_alias': 'review-db'}
models/UserModel.py
from mongoengine import Document
from mongoengine.fields import StringField, IntField


class UserModel(Document):
    username = StringField()
    age = IntField()
    meta = {'db_alias': 'user-db'}

3. Services

I wasn't very creative on the service. I just called them service1 and service2. Service1 is handling the users and Service2 the reviews.
Both have the same structure:

serviceX
|
|-app.py
|-config.py
|-schema.py
|-server.py
server.py

The server.py file is the same in both services, except for the port to avoid conflicts. I am using a UploadView. We are using File uploads, so this a custom option.

from app import app
from graphene_file_upload.flask import FileUploadGraphQLView
from mongoengine import connect
from schema import schema

# we need to connect to both databases
connect('service1',
        host=app.config['MONGO_HOST'],
        alias='user-db',
        username=app.config['MONGO_USER'],
        password=app.config['MONGO_PWD'],
        authentication_source="admin")

connect('service2',
        host=app.config['MONGO_HOST'],
        alias='review-db',
        username=app.config['MONGO_USER'],
        password=app.config['MONGO_PWD'],
        authentication_source="admin")

app.add_url_rule('/graphql', view_func=FileUploadGraphQLView.as_view('graphql', schema=schema, graphiql=app.debug))

if __name__ == '__main__':
    app.run(port=5000)
app.py

The app.py is even simpler.

from flask import Flask

app = Flask(__name__)
app.debug = True

app.config.from_object('config.DevConfig')
config.py

Nothing special here. Fill in your own details.

class BaseConfig:
    TESTING = False
    DEBUG = False

    MONGO_USER = '******'
    MONGO_PWD = '*****'

    MONGO_HOST = '*******'


class DevConfig(BaseConfig):
    DEBUG = True
schema.py

The schema file is the big difference in the two services. Let's start by the User.

service1
import graphene
from graphene_mongo import MongoengineObjectType

from graphene_federation import build_schema, key
from models.UserModel import UserModel


# The primary key 
@key('id')
class User(MongoengineObjectType):

    # we resolve a db reference by the id
    def __resolve_reference(self, info, **kwargs):
        # common mongoengine query
        return UserModel.objects.get(id=self.id)

    # Use Model as Type (common graphene_mongoengine)
    class Meta:
        model = UserModel


# define a query
class Query(graphene.ObjectType):
    users = graphene.Field(User)

    def resolve_users(self, info, **kwargs):
        return UserModel.objects.all()


# define a mutation
class CreateUser(graphene.Mutation):
    user = graphene.Field(User)

    class Arguments:
        username = graphene.String()
        age = graphene.Int()

    def mutate(self, info, username, age):
        user = UserModel(username=username, age=age)
        user.save()
        return CreateUser(user)


class Mutation(graphene.ObjectType):
    create_user = CreateUser.Field()


# build schema USE THE FEDERATION PACKAGE
schema = build_schema(Query, types=[User], mutation=Mutation)

As you can see, nothing special happens here. We just need to set the id as our key and add a resolver for the key.
Service2 will now be able to resolve a type from another schema by using the external function.

service2
import graphene
from graphene_mongo import MongoengineObjectType

from graphene_federation import build_schema, key, external, extend
from models.ReviewModel import ReviewModel


# use extend and the key to tell service2 that this a type from another service
@extend('id')
class User(graphene.ObjectType):
    # define key, use graphene.ID because this is the type used by graphene_mongo
    # set it to external
    id = external(graphene.ID())


# set id as key
@key('id')
class Review(MongoengineObjectType):
    # Add user as type
    user = graphene.Field(User)

    # optional: we dont need to resolve the reference
    def __resolve_reference(self, info, **kwargs):
        return ReviewModel.objects.get(id=self.id)

    # Use Model as Type (common graphene_mongoengine)
    class Meta:
        model = ReviewModel


# define a query
class Query(graphene.ObjectType):
    reviews = graphene.Field(Review)

    def resolve_reviews(self, info, **kwargs):
        return ReviewModel.objects.all().first()


# define a mutation
class CreateReview(graphene.Mutation):
    review = graphene.Field(Review)

    class Arguments:
        name = graphene.String()
        user_id = graphene.String()

    def mutate(self, info, name, user_id):
        review = ReviewModel(name=name, user=user_id)
        review.save()
        return CreateReview(review)


class Mutation(graphene.ObjectType):
    create_review = CreateReview.Field()


# build schema USE THE FEDERATION PACKAGE
schema = build_schema(Query, mutation=Mutation)

4. Gateway

To combine our 2 Services into one gateway, we use the Apollo Gateway.
We just need to create the following file:

gateway.js
const { ApolloServer } = require('apollo-server');
const { ApolloGateway } = require("@apollo/gateway");

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'service1', url: 'http://127.0.0.1:5000/graphql' },
    { name: 'service2', url: 'http://127.0.0.1:5001/graphql' },
    // more services
  ],
});

const server = new ApolloServer({
  gateway,

  // Currently, subscriptions are enabled by default with Apollo Server, however,
  // subscriptions are not compatible with the gateway.  We hope to resolve this
  // limitation in future versions of Apollo Server.  Please reach out to us on
  // https://spectrum.chat/apollo/apollo-server if this is critical to your adoption!
  subscriptions: false,
});

server.listen().then(({ url }) => {
  console.log(`๐Ÿš€ Server ready at ${url}`);
});

Next we need to install the depencies:

npm install @apollo/gateway apollo-server graphql

5. Start it up

The last thing is to just run the two flask applications and start the gateway via
node gateway.js

I hope all the instructions are clear enough. Have a great day!!!

Support for Apollo Federation 2

Hello there! ๐Ÿ‘‹

Is there any plan for supporting Apollo Federation 2? If not, I'm happy to help with that. I've added support to Strawberry GraphQL recently and I'm happy to help here too ๐Ÿ˜Š

The main changes in Federation 2 are the addition of new directives:

Library uses global variables so it means that only one schema can be defined at a time

In case you have an app with 2 schemas (typically often done in unit tests), since the list of entity is stored in a global variable the two schemas collide.

Simple test to reproduce it:

from graphene import ObjectType, ID, Field
from graphene_federation import key, build_schema

@key("id")
class User(ObjectType):
     id = ID(required=True)

class UserQuery(ObjectType):
    user = Field(User)

user_schema = build_schema(query=UserQuery)

@key("id")
class Message(ObjectType):
     id = ID(required=True)

class MessageQuery(ObjectType):
    message = Field(Message)

message_schema = build_schema(query=MessageQuery)

print(message_schema)

returns the following schema print:

schema {
  query: Query
}

type Message {
  id: ID!
}

type Query {
  message: Message
  _entities(representations: [_Any]): [_Entity]
  _service: _Service
}

type User {
  id: ID!
}

scalar _Any

union _Entity = User | Message

type _Service {
  sdl: String
}

So as you can see it contains User type... which it shouldn't!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.