Giter Club home page Giter Club logo

py-graphql-client's Introduction

py-graphql-client

Dead-simple to use GraphQL client over websocket. Using the apollo-transport-ws protocol.

Install

pip install py-graphql-client

Examples

Setup subscriptions super easily

from graphql_client import GraphQLClient

query = """
  subscription {
    notifications {
      id
      title
      content
    }
  }
"""

def callback(_id, data):
  print("got new data..")
  print(f"msg id: {_id}. data: {data}")

with GraphQLClient('ws://localhost:8080/graphql') as client:
  sub_id = client.subscribe(query, callback=callback)
  # do other stuff
  # ...
  # later stop the subscription
  client.stop_subscribe(sub_id)

Variables can be passed

from graphql_client import GraphQLClient

query = """
    subscription ($limit: Int!) {
      notifications (order_by: {created: "desc"}, limit: $limit) {
        id
        title
        content
      }
    }
  """

def callback(_id, data):
  print("got new data..")
  print(f"msg id: {_id}. data: {data}")

with GraphQLClient('ws://localhost:8080/graphql') as client:
  sub_id = client.subscribe(query, variables={'limit': 10}, callback=callback)
  # ...

Headers can be passed too

from graphql_client import GraphQLClient

query = """
    subscription ($limit: Int!) {
      notifications (order_by: {created: "desc"}, limit: $limit) {
        id
        title
        content
      }
    }
  """

def callback(_id, data):
  print("got new data..")
  print(f"msg id: {_id}. data: {data}")

with GraphQLClient('ws://localhost:8080/graphql') as client:
  sub_id = client.subscribe(query,
                            variables={'limit': 10},
                            headers={'Authorization': 'Bearer xxxx'},
                            callback=callback)
  ...
  client.stop_subscribe(sub_id)

Normal queries and mutations work too

from graphql_client import GraphQLClient

query = """
  query ($limit: Int!) {
    notifications (order_by: {created: "desc"}, limit: $limit) {
      id
      title
      content
    }
  }
"""

with GraphQLClient('ws://localhost:8080/graphql') as client:
    res = client.query(query, variables={'limit': 10}, headers={'Authorization': 'Bearer xxxx'})
    print(res)

Without the context manager API

from graphql_client import GraphQLClient

query = """
  query ($limit: Int!) {
    notifications (order_by: {created: "desc"}, limit: $limit) {
      id
      title
      content
    }
  }
"""

client = GraphQLClient('ws://localhost:8080/graphql')
res = client.query(query, variables={'limit': 10}, headers={'Authorization': 'Bearer xxxx'})
print(res)
client.close()

TODO

  • support http as well
  • should use asyncio websocket library?

py-graphql-client's People

Contributors

alemuzzi avatar anthonyhiga avatar bharathaaravind avatar ecthiender avatar salmonmode avatar timguitediamond 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

Watchers

 avatar  avatar  avatar  avatar

py-graphql-client's Issues

Websocket exception when subscribe

(.venv) โžœ  fetcher python main.py
--- request header ---
GET /graphql HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8080
Origin: http://localhost:8080
Sec-WebSocket-Key: Y/T3i5rm6rUT2DcY1WBSHQ==
Sec-WebSocket-Version: 13


-----------------------
--- response header ---
HTTP/1.1 101 Switching Protocols
Server: nginx/1.17.7
Date: Sun, 12 Jan 2020 16:05:14 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: YjXSZB3ABv2zrPKFRHWEQF4t4oQ=
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS
Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization
Strict-Transport-Security: max-age=15724800; includeSubDomains
-----------------------
send: b'\x81\xb9e\xd2\xe5\x7f\x1e\xf0\x91\x06\x15\xb7\xc7EE\xf0\x86\x10\x0b\xbc\x80\x1c\x11\xbb\x8a\x11:\xbb\x8b\x16\x11\xf0\xc9_G\xa2\x84\x06\t\xbd\x84\x1bG\xe8\xc5\x04G\xba\x80\x1e\x01\xb7\x97\x0cG\xe8\xc5\x11\x10\xbe\x89\x02\x18'
send: b'\x88\x82\x07\x08\x97\xd2\x04\xe0'
send: b'\x81\xfe\x00\x9e[\x92K\xb8 \xb0"\xdcy\xa8k\x9ak\xe2z\xe8n\xd1i\x94{\xb0?\xc1+\xf7i\x82{\xb08\xcc:\xe0?\x9aw\xb2i\xc8:\xeb\'\xd7:\xf6i\x82{\xe9i\xd0>\xf3/\xdd)\xe1i\x82{\xfc>\xd47\xbek\x9a*\xe7.\xca"\xb0q\x98y\xce%\xcb.\xf08\xdb)\xfb;\xcc2\xfd%\x98 \xce%\x98{\xf0\'\xd78\xf98\x98s\xe96\x91{\xe9\x17\xd6{\xb2k\x982\xf6k\xcb/\xf3?\xcd(\xcd%\xd96\xf7\x17\xd6{\xb26\xe45\xef\x17\xd6y\xbek\x9a-\xf39\xd1:\xf0\'\xdd(\xb0q\x985\xe7\'\xd4&\xef'
Traceback (most recent call last):
  File "main.py", line 25, in <module>
    id = ws.subscribe(query, callback=callback)
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/graphql_client/__init__.py", line 79, in subscribe
    _id = self._start(payload)
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/graphql_client/__init__.py", line 58, in _start
    self._conn.recv()
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_core.py", line 310, in recv
    opcode, data = self.recv_data()
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_core.py", line 327, in recv_data
    opcode, frame = self.recv_data_frame(control_frame)
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_core.py", line 340, in recv_data_frame
    frame = self.recv_frame()
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_core.py", line 374, in recv_frame
    return self.frame_buffer.recv_frame()
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_abnf.py", line 361, in recv_frame
    self.recv_header()
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_abnf.py", line 309, in recv_header
    header = self.recv_strict(2)
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_abnf.py", line 396, in recv_strict
    bytes_ = self.recv(min(16384, shortage))
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_core.py", line 449, in _recv
    return recv(self.sock, bufsize)
  File "/Users/rd/Code/fetcher/.venv/lib/python3.7/site-packages/websocket/_socket.py", line 94, in recv
    "Connection is already closed.")
websocket._exceptions.WebSocketConnectionClosedException: Connection is already closed.

How to skip ssl certificate verification?

Is there a way to send some flag to ignore ssl certificate check?

I have one environment which is failing to connect with below message:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)

Connection crashes when Websocket connection closed by server

If there is an open subscription the client should automatically retry / reconnect, maybe with an exponential backoff. I have found the tenacity package helpful for this in the past.

Exception has occurred: WebSocketConnectionClosedException
Connection is already closed.
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_socket.py", line 93, in recv
    raise WebSocketConnectionClosedException(
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_core.py", line 449, in _recv
    return recv(self.sock, bufsize)
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_abnf.py", line 396, in recv_strict
    bytes_ = self.recv(min(16384, shortage))
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_abnf.py", line 309, in recv_header
    header = self.recv_strict(2)
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_abnf.py", line 361, in recv_frame
    self.recv_header()
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_core.py", line 374, in recv_frame
    return self.frame_buffer.recv_frame()
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_core.py", line 340, in recv_data_frame
    frame = self.recv_frame()
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_core.py", line 327, in recv_data
    opcode, frame = self.recv_data_frame(control_frame)
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/websocket/_core.py", line 310, in recv
    opcode, data = self.recv_data()
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/graphql_client/__init__.py", line 85, in _receiver_task
    res = self._connection.recv()
  File "/usr/local/python/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/python/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/local/python/lib/python3.8/threading.py", line 890, in _bootstrap
    self._bootstrap_inner()

"cannot join current thread" when close() called on running client

On Python 3.8.12 with py-graphql-client 0.1.1 and websockets-client 0.54.0 running on Ubuntu focal, I see this exception when I call close():

stopping 5990f26aabc1472bad9913d8d3cf34b9
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/local/python/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/local/python/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/graphql_client/__init__.py", line 118, in _receiver_task
    user_fn(op_id, msg)
  File "src/python/serenity/core/utils/rfb_check.py", line 17, in callback
    client.close()
  File "/workspaces/serenity.core/venv/lib/python3.8/site-packages/graphql_client/__init__.py", line 239, in close
    self._recevier_thread.join()
  File "/usr/local/python/lib/python3.8/threading.py", line 1008, in join
    raise RuntimeError("cannot join current thread")
RuntimeError: cannot join current thread

Example that reproduces the issue:

from time import sleep

from graphql_client import GraphQLClient


client = GraphQLClient('ws://localhost:9002/graphql/subscriptions')
sub_ids = []


def callback(_id, data):
    if data['type'] == 'complete':
        for sub_id in sub_ids:
            print(f'stopping {sub_id}')
            client.stop_subscribe(sub_id)
            client.close()
    else:
        print(f"msg id: {_id}. data: {data}")


query = """
subscription {
    overallPortfolioSummaries {
        weight
    }
}
"""

sub_ids.append(client.subscribe(query, callback=callback))

Unable to import module

I'm running Python 3.5.2 on Ubuntu. Attempting to import GraphQLClient throws the following:

Traceback (most recent call last): File "this_one.py", line 1, in <module> from graphql_client import GraphQLClient File "/path/lib/python3.5/site-packages/graphql_client/__init__.py", line 102 err = f'Protocol Violation.\nExpected "id" in {msg}, but could not find.' ^ SyntaxError: invalid syntax

Looking into Python3 f-strings, it looks like they aren't supported until 3.6, but your docs state this lib just requires >= 3.4.

Request to expose websocket ConnectionId

Can I get the Id of the web socket connection exposed on the GraphQlClient object? I wanted to use it for logging purposes and reconciliation with the server side records

how to use subscriptions ?

HI ,

THIS MODULES HELPS US TO SUBSCRIPT TO A SERVICE , AFTER SUBSCRIPTION WE NEED TO KEEP TO CONNECTION OPEN TO RECEIVE POST REQUESTS WHEN EVER SERVER SENDS US , HOW DO WE HANDLE THAT ?

First message is lost when using subscribe/query method

When we set up a subscription the _start method is consuming the first message sent by the subscriber which should not be happening. All the messages should be sent to the callback method passed.

The same applies to the query method.

support for context manager or `with` statement

Support GraphQLClient to be used with a context manage a.k.a with statement.

E.g -

with GraphQLClient('ws://localhost:8080/graphql') as client:
    sub_id = client.subscribe(..)
    ...
    client.stop_subscribe(sub_id)

Subscription parameters wrapped in "headers" attribute

Hi,

I'm looking to pass some auth information in subscription parameters, but the payload for the connection initialization wraps the headers in a nested headers attribute (here, instead of passing them as-is.

This assumes that the target server looks inside a headers property on the incoming parameters - different servers handle these payloads differently. Some added flexibility to the client library will make it much more versatile and usable against various servers.

I will submit a quick PR to address this, but looking forward to hearing any thoughts!

Can you give an SSL example?

If I connect to a URL of the form wss://... I get an SSL error, but is SSL supported? I guess you need to provide a way to pass a native SSL context?

context manager bug

running the simple example on the homepage

from graphql_client import GraphQLClient


def callback(_id, data):
  print("got new data..")
  print(f"msg id: {_id}. data: {data}")

with GraphQLClient(endpoint) as client:
  sub_id = client.subscribe(subscription,
                            # variables={'limit': 10},
                            headers=new_header,
                            callback=callback)
  
  client.stop_subscribe(sub_id)

returns the following:

raceback (most recent call last):
  File "dro_auth.py", line 91, in <module>
    with GraphQLClient(endpoint) as client:
AttributeError: __enter__

Feedback: Multiple subscription callbacks in one thread

Hey, obviously an issue probably isn't the best place to put this but there aren't many other feedback mechanisms on Github!

Just wanted to say thanks for your work! I mentioned before about more efficiently adding multiple callbacks to a single thread, was going to look at it today but it seems that your changes in e48ea36 have already addressed it!

From the tests I ran this morning it seems to be working well, with only one thread handling many separate subscriptions. I will test a bit further and put feedback here if that's alright?

Let me know if there are any other tasks you would like helping with for this package ๐Ÿ˜„

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.