在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):datadvance/DjangoChannelsGraphqlWs开源软件地址(OpenSource Url):https://github.com/datadvance/DjangoChannelsGraphqlWs开源编程语言(OpenSource Language):Python 98.9%开源软件介绍(OpenSource Introduction):Django Channels based WebSocket GraphQL server with Graphene-like subscriptions
Features
Installationpip install django-channels-graphql-ws Getting startedCreate a GraphQL schema using Graphene. Note the import channels_graphql_ws
import graphene
class MySubscription(channels_graphql_ws.Subscription):
"""Simple GraphQL subscription."""
# Leave only latest 64 messages in the server queue.
notification_queue_limit = 64
# Subscription payload.
event = graphene.String()
class Arguments:
"""That is how subscription arguments are defined."""
arg1 = graphene.String()
arg2 = graphene.String()
@staticmethod
def subscribe(root, info, arg1, arg2):
"""Called when user subscribes."""
# Return the list of subscription group names.
return ["group42"]
@staticmethod
def publish(payload, info, arg1, arg2):
"""Called to notify the client."""
# Here `payload` contains the `payload` from the `broadcast()`
# invocation (see below). You can return `MySubscription.SKIP`
# if you wish to suppress the notification to a particular
# client. For example, this allows to avoid notifications for
# the actions made by this particular client.
return MySubscription(event="Something has happened!")
class Query(graphene.ObjectType):
"""Root GraphQL query."""
# Check Graphene docs to see how to define queries.
pass
class Mutation(graphene.ObjectType):
"""Root GraphQL mutation."""
# Check Graphene docs to see how to define mutations.
pass
class Subscription(graphene.ObjectType):
"""Root GraphQL subscription."""
my_subscription = MySubscription.Field()
graphql_schema = graphene.Schema(
query=Query,
mutation=Mutation,
subscription=Subscription,
) Make your own WebSocket consumer subclass and set the schema it serves: class MyGraphqlWsConsumer(channels_graphql_ws.GraphqlWsConsumer):
"""Channels WebSocket consumer which provides GraphQL API."""
schema = graphql_schema
# Uncomment to send keepalive message every 42 seconds.
# send_keepalive_every = 42
# Uncomment to process requests sequentially (useful for tests).
# strict_ordering = True
async def on_connect(self, payload):
"""New client connection handler."""
# You can `raise` from here to reject the connection.
print("New client connected!") Setup Django Channels routing: application = channels.routing.ProtocolTypeRouter({
"websocket": channels.routing.URLRouter([
django.urls.path("graphql/", MyGraphqlWsConsumer.as_asgi()),
])
}) Notify﹡ clients when some event happens using
the MySubscription.broadcast(
# Subscription group to notify clients in.
group="group42",
# Dict delivered to the `publish` method.
payload={},
) Notify﹡ clients in an coroutine function
using the await MySubscription.broadcast(
# Subscription group to notify clients in.
group="group42",
# Dict delivered to the `publish` method.
payload={},
) ﹡) In case you are testing your client code by notifying it from the Django Shell, you have to setup a channel layer in order for the two instance of your application. The same applies in production with workers. ExampleYou can find simple usage example in the example directory. Run: cd example/
# Initialize database.
./manage.py migrate
# Create "user" with password "user".
./manage.py createsuperuser
# Run development server.
./manage.py runserver Play with the API though the GraphiQL browser at http://127.0.0.1:8000. You can start with the following GraphQL requests: # Check there are no messages.
query read { history(chatroom: "kittens") { chatroom text sender }}
# Send a message as Anonymous.
mutation send { sendChatMessage(chatroom: "kittens", text: "Hi all!"){ ok }}
# Check there is a message from `Anonymous`.
query read { history(chatroom: "kittens") { text sender } }
# Login as `user`.
mutation send { login(username: "user", password: "pass") { ok } }
# Send a message as a `user`.
mutation send { sendChatMessage(chatroom: "kittens", text: "It is me!"){ ok }}
# Check there is a message from both `Anonymous` and from `user`.
query read { history(chatroom: "kittens") { text sender } }
# Subscribe, do this from a separate browser tab, it waits for events.
subscription s { onNewChatMessage(chatroom: "kittens") { text sender }}
# Send something again to check subscription triggers.
mutation send { sendChatMessage(chatroom: "kittens", text: "Something ;-)!"){ ok }} DetailsThe
For details check the source code which is thoroughly commented. The docstrings of classes are especially useful. Since the WebSocket handling is based on the Django Channels and subscriptions are implemented in the Graphene-like style it is recommended to have a look the documentation of these great projects: The implemented WebSocket-based protocol was taken from the library subscription-transport-ws which is used by the Apollo GraphQL. Check the protocol description for details. Automatic Django model serializationThe Execution
ContextThe context object ( def resolve_something(self, info):
info.context.fortytwo = 42
assert info.context["fortytwo"] == 42 AuthenticationTo enable authentication it is typically enough to wrap your ASGI
application into the application = channels.routing.ProtocolTypeRouter({
"websocket": channels.auth.AuthMiddlewareStack(
channels.routing.URLRouter([
django.urls.path("graphql/", MyGraphqlWsConsumer),
])
),
}) This gives you a Django user class Login(graphene.Mutation, name="LoginPayload"):
"""Login mutation."""
ok = graphene.Boolean(required=True)
class Arguments:
"""Login request arguments."""
username = graphene.String(required=True)
password = graphene.String(required=True)
def mutate(self, info, username, password):
"""Login request."""
# Ask Django to authenticate user.
user = django.contrib.auth.authenticate(username=username, password=password)
if user is None:
return Login(ok=False)
# Use Channels to login, in other words to put proper data to
# the session stored in the scope. The `info.context` is
# practically just a wrapper around Channel `self.scope`, but
# the `login` method requires dict, so use `_asdict`.
asgiref.sync.async_to_sync(channels.auth.login)(info.context._asdict(), user)
# Save the session,cause `channels.auth.login` does not do this.
info.context.session.save()
return Login(ok=True) The authentication is based on the Channels authentication mechanisms. Check the Channels documentation. Also take a look at the example in the example directory. The Python clientThere is the transport = channels_graphql_ws.GraphqlWsTransportAiohttp(
"ws://backend.endpoint/graphql/", cookies={"sessionid": session_id}
)
client = channels_graphql_ws.GraphqlWsClient(transport)
await client.connect_and_init()
result = await client.execute("query { users { id login email name } }")
users = result["data"]
await client.finalize() See the The GraphiQL clientThe GraphiQL provided by Graphene doesn't connect to your GraphQL
endpoint via WebSocket ; instead you should use a modified GraphiQL
template under TestingTo test GraphQL WebSocket API read the appropriate page in the Channels documentation. In order to simplify unit testing there is a Subscription activation confirmationThe original Apollo's protocol does not allow client to know when a
subscription activates. This inevitably leads to the race conditions on
the client side. Sometimes it is not that crucial, but there are cases
when this leads to serious issues.
Here is the discussion
in the
To solve this problem, there is the To customize the confirmation message itself set the GraphQL middlewareIt is possible to inject middleware into the GraphQL operation
processing. For that define def my_middleware(next_middleware, root, info, *args, **kwds):
"""My custom GraphQL middleware."""
# Invoke next middleware.
return next_middleware(root, info, *args, **kwds)
class MyGraphqlWsConsumer(channels_graphql_ws.GraphqlWsConsumer):
...
middleware = [my_middleware] For more information about GraphQL middleware please take a look at the relevant section in the Graphene documentation. AlternativesThere is a Tomáš Ehrlich GitHubGist GraphQL Subscription with django-channels which this implementation was initially based on. There is a promising GraphQL WS library by the Graphene authors. In particular this pull request gives a hope that there will be native Graphene implementation of the WebSocket transport with subscriptions one day. DevelopmentBootstrapA reminder of how to setup an environment for the development.
Use: Running testsA reminder of how to run tests.
Making releaseA reminder of how to make and publish a new release.
ContributingThis project is developed and maintained by DATADVANCE LLC. Please submit an issue if you have any questions or want to suggest an improvement. AcknowledgementsThis work is supported by the Russian Foundation for Basic Research (project No. 15-29-07043). |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论