preply / graphene-federation Goto Github PK
View Code? Open in Web Editor NEWFederation support for graphene
License: MIT License
Federation support for graphene
License: MIT License
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!
As you can see in this example when printing the annotated schema in _mark_field
method:
type User @key(fields: "id") {
id: ID! @external
files(before: String, after: String, first: Int, last: Int): FileConnection
}
type User @key(fields: "id") {
id: ID!
files(before: String, after: String, first: Int, last: Int, id: ID): @external FileConnection
}
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.
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'))])
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!
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.
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
PS: it was early morning, meanwhile there is an workaround, you actually can call _entities resolver but it must be in following form
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
consider moving dos to RTFM
Hi,
I have tried to develop a required_login decorator for graphene federation.
I dont know if it will be useful, or if it is well done (i am not a dev).
But as i developed it for my own need, i am sharing it and maybe someone would be interested about reviewing it, modifying it and improving
https://github.com/troptop/graphene_federation_required_login
Best regards
Please check it out. We need that so hard.
There was recently a 0.2.0 release on Jul 13, 2022 in pypi: https://pypi.org/project/graphene-federation/#files
However, I'm having trouble finding the associated release: https://github.com/preply/graphene-federation/releases
I'm also not seeing a 0.2.0 version bump on the master branch: https://github.com/preply/graphene-federation/commits/master
Can you add the code for the 0.2.0 release to the master branch and cut a release for it?
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?
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!
}
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 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
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?
Seems that there is no activity since the initial release. Should I consider this project dead? Is there a plan to find new contributors?
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?
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?
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.
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?
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.
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
We have two DB-Models, a User
and a Review
. Each of the models is sitting in its own Database. They are only connected via Mongoengine.
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'}
from mongoengine import Document
from mongoengine.fields import StringField, IntField
class UserModel(Document):
username = StringField()
age = IntField()
meta = {'db_alias': 'user-db'}
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
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)
The app.py is even simpler.
from flask import Flask
app = Flask(__name__)
app.debug = True
app.config.from_object('config.DevConfig')
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
The schema file is the big difference in the two services. Let's start by the User.
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.
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)
To combine our 2 Services into one gateway, we use the Apollo Gateway.
We just need to create the following file:
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
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!!!
when using
@key('my_id')
class MyModel:
my_id
/graphql
ends up converting my_id
to myId
but the key stays as my_id
resulting in client error
This data graph is missing a valid configuration. MyModel -> @key selects my_id, but MyModel.my_id could not be found
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:
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.