Description
Hello @syrusakbary.
Thanks for all your hard work on graphene and graphql-python. Awesome library!!
I posted this on #393 earlier this week...reposting here so it's easier to discover.
I implemented a port of the apollo graphql subscriptions modules (graphql-subscriptions and subscriptions-transport-ws) for graphene / python. They work w/ apollo-client.
It is here.
Same basic api as the Apollo modules. It is still very rough...but works so far, based on my limited internal testing. Uses redis-py, gevent-websockets, and syrusakbary/promises. I was going to add a simple example app, setup.py for easier install, and more info to the readme w/ the API, in the next few days. A brief example is below. Only works on python2 for now. My plan is to start working on tests as well. I figured I'd go ahead and share in this early stage in case anybody is interested...
I'm very new to open source, so any critiques or pull requests are welcome.
Simple example:
Server (using Flask and Flask-Sockets):
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_sockets import Sockets
from .subscription_manager import SubscriptionManager, RedisPubsub
from .subscription_transport_ws import ApolloSubscriptionServer
app = Flask(__name__)
sockets = Sockets(app)
pubsub = RedisPubsub()
schema = graphene.Schema(
query=Query,
mutation=Mutation,
subscription=Subscription
)
subscription_mgr = SubscriptionManager(schema, pubsub)
@sockets.route('/socket')
def socket_channel(websocket):
subscription_server = ApolloSubscriptionServer(subscription_mgr, websocket)
subscription_server.handle()
return []
if __name__ == "__main__":
from geventwebsocket import WebSocketServer
server = WebSocketServer(('', 5000), app)
print ' Serving at host 0.0.0.0:5000...\n'
server.serve_forever()
Of course on the server you have to "publish" each time you have a mutation (in this case to a redis channel). That could look something like this (using graphene / sql-alchemy):
class Subscription(graphene.ObjectType):
users = graphene_sqlalchemy.SQLAlchemyConnectionField(
User,
active=graphene.Boolean()
)
def resolve_users(self, args, context, info):
query = User.get_query(context)
return query.filter_by(id=info.root_value.get('id'))
class AddUser(graphene.ClientIDMutation):
class Input:
username = graphene.String(required=True)
email = graphene.String()
ok = graphene.Boolean()
user = graphene.Field(lambda: User)
@classmethod
def mutate_and_get_payload(cls, args, context, info):
_input = args.copy()
del _input['clientMutationId']
new_user = UserModel(**_input)
db.session.add(new_user)
db.session.commit()
ok = True
if pubsub.subscriptions:
pubsub.publish('users', new_user.as_dict())
return AddUser(ok=ok, user=new_user)
Client (using react-apollo client):
import React from 'react'
import ReactDOM from 'react-dom'
import { graphql, ApolloProvider } from 'react-apollo'
import gql from 'graphql-tag'
import ApolloClient, { createNetworkInterface } from 'apollo-client'
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws'
import ChatApp from './screens/ChatApp'
import ListBox from '../components/ListBox'
const SUBSCRIPTION_QUERY = gql`
subscription newUsers {
users(active: true) {
edges {
node {
id
username
}
}
}
}
`
const LIST_BOX_QUERY = gql`
query AllUsers {
users(active: true) {
edges {
node {
id
username
}
}
}
}
`
class ChatListBox extends React.Component {
componentWillReceiveProps(newProps) {
if (!newProps.data.loading) {
if (this.subscription) {
return
}
this.subscription = newProps.data.subscribeToMore({
document: SUBSCRIPTION_QUERY,
updateQuery: (previousResult, {subscriptionData}) => {
const newUser = subscriptionData.data.users.edges
const newResult = {
users: {
edges: [
...previousResult.users.edges,
...newUser
]
}
}
return newResult
},
onError: (err) => console.error(err)
})
}
}
render() {
return <ListBox data={this.props.data} />
}
}
const ChatListBoxWithData = graphql(LIST_BOX_QUERY)(ChatListBox)
export default ChatListBoxWithData
const networkInterface = createNetworkInterface({
uri: 'http://localhost:5000/graphql'
})
const wsClient = new SubscriptionClient(`ws://localhost:5000/socket`, {
reconnect: true
})
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface,
wsClient,
)
const client = new ApolloClient({
dataIdFromObject: o => o.id,
networkInterface: networkInterfaceWithSubscriptions
})
ReactDOM.render(
<ApolloProvider client={client}>
<ChatApp />
</ApolloProvider>,
document.getElementById('root')
)