Python GraphQL query with variables?

New Member
7 0 0

I'm interacting with the API in Python and am struggling to move my query to GraphQL. I'm wondering if anyone has an  example they could share of how to use use graphql with requests.post() while passing in variables. 

 

I'm trying to get all products sold after a specific date. The basic query works OK, but i'm struggling to add variables to the query, eventually I'd like to paginate through the responses, but currently i'm just trying to get the formatting correct. 

 

import requests
def get_orders():
    out = []  # the final output list of dictionaries to be converted to a DF
    shop = 'shopname'
    token = 'real-token-would-go-here'
    headers = {
        'Content-Type': 'application/graphql',
        'X-Shopify-Access-Token': f'{token}',
    }



    data = '''
query ($orderNum: Int!, $cursor: String)
{
  orders(query:"created_at:>2019-04-04", first: $orderNum, after: $cursor) {
    pageInfo {
      hasNextPage
      hasPreviousPage
    }
    edges {
      cursor
      node {
        name
        processedAt
        lineItems(first: 20) {
          edges {
            node {
              title
              variantTitle
              quantity
              variant {
                barcode
                sku
              }
              product{
                productType

                
              }
            }
          }
        }
      }
    }
  }
}

'''
    variables = '''{
        'orderNum': 3
        }
        '''
    response = requests.post(f'https://{shop}.myshopify.com/admin/api/2019-07/graphql.json', json={'query': data, 'variables': variables}, headers=headers)
    print(f'response json is: {response.json()}')

get_orders()

This is the response I get: 

 

response json is: {'errors': [{'message': 'Parse error on "query" (STRING) at [1, 2]', 'locations': [{'line': 1, 'column': 2}]}]}

 

0 Likes
Tourist
3 0 0

Sorry, I am also just at the beginning of the GraphQl journey and can't answer you question directly. But I have written a little graphql client, which I used successfully to interact with the GraphQl Admin Endpoint:

 

class SyGraphQlException(Exception):

    def __init__(self, http_status_code, reason):
        self.http_status_code = http_status_code
        self.reason = reason


class SyGqlClient:

    def __init__(self, shop_name, access_token):
        print('WARNING: deprecated. Please use sgqlc - Simple GraphQL Client https://github.com/profusion/sgqlc for further development.', file=sys.stderr)
        self.url = 'https://{}.myshopify.com/admin/api/graphql.json'.format(
            shop_name)
        self.headers = {
            "Content-Type": "application/json",
            "X-Shopify-Access-Token": access_token
        }

    def execute(self, query, variables=None):
        json = dict({'query': query})
        if variables:
            json['variables'] = variables

        request = requests.post(
            self.url,
            json=json,
            headers=self.headers)

        if request.ok:
            result = request.json()
            return result
        else:
            raise SyGraphQlException(request.status_code, request.reason)

    def wait_for_job(self, job_id: str):
        gq = """query GetJobStatus($id: ID!) {
                    shop {
                        name
                    }
                    job(id: $id) {
                        id
                        done
                    }
                }"""
        resp = self.execute(query=gq, variables={"id": str(job_id)})
        while resp['data']['job']['done'] != True:
            print('job not finished, wait 5 seconds.')
            time.sleep(5)
            resp = self.execute(query=gq, variables={"id": str(job_id)})

As you can see, there are tools like sgqlc - Simple GraphQL Client to interact with a graph ql endpoint. But it was too much of a hassle for me up to now. But I really think this is the way to go soon.

 

This is an example on how to use it:

sy_gql_client = sy_commons.SyGqlClient(SHOP_NAME, PASSWORD)

collection_handle='this-is-a-collection'
gq = """query collectionProducts($collection_name: String!, $page_size: Int!, $cursor: String) {
                    shop {
                        name
                    }
                    collectionByHandle(handle: $collection_name) {
                        id
                        handle
                        sortOrder
                        products(first: $page_size, after: $cursor) {
                            pageInfo {
                                hasNextPage
                            }
                            edges {
                                cursor
                                node {
                                    id
                                    handle
                                    legacyResourceId
                                    tags
                                    collections(first: 15) {
                                        edges {
                                            node {
                                                id
                                                handle
                                            }
                                            cursor
                                        }
                                        pageInfo {
                                            hasNextPage
                                        }
                                    }
                                }
                            }
                        }
                    }
                }"""
        variables = dict({
            "collection_name": collection_handle,
            "page_size": 25,
            "cursor": None
        })
        resp = sy_graphql_client.execute(gq, variables)
        sort_order = resp['data']['collectionByHandle']['sortOrder']

I hope this helps! I remember I had been buzzled as well on my first approach of this ;)

0 Likes
New Member
7 0 0

Thank you for the reply, that does help a lot. A few follow up questions:

 

- I see that you have a wait function built in, but it doesn't look like it's doing any math based on the query cost. Is the idea that it just maxes out and then waits 5 seconds before restarting? 

 

- it doesn't look like it'll continue to follow a cursor. Have you made a function that could be added to this class to follow paginated responses? 

 

- how/when do you use the "wait_for_job" function? 

0 Likes
Highlighted
Tourist
3 0 0

 

- I see that you have a wait function built in, but it doesn't look like it's doing any math based on the query cost. Is the idea that it just maxes out and then waits 5 seconds before restarting? 

 

There are some migrations which take a while to finish. As I need to wait to finish to go on, I built this. Here is an example snippet:

                gq = """mutation ReorderProducts($id: ID!, $moves: [MoveInput!]!) {
                        collectionReorderProducts(id: $id, moves: $moves) {
                            job {
                                id
                                }
                                userErrors {
                                    field
                                    message
                                }
                            }
                        }
                        """
                resp = self.sy_graphql_client.execute(
                    query=gq,
                    variables={
                        "id": before_collection_meta.coll_meta.id,
                        "moves": list(map(lambda mtc: {"id": mtc.id, "newPosition": mtc.new_position}, move_to_commands))})

                reorder_job_id = resp["data"]["collectionReorderProducts"]["job"]["id"]
                self.sy_graphql_client.wait_for_job(reorder_job_id)

The move to commands are a list of moves, which are converted into the expected format with the lambda...

 

- it doesn't look like it'll continue to follow a cursor. Have you made a function that could be added to this class to follow paginated responses? 

You can give it a query with cursor as well. But need to execute it several times, and give the cursor as variable. I intentionally designed it like this, as we have too many products in our shop to load all of them and iterate over them later on...


 

0 Likes
Shopify Staff
Shopify Staff
1555 79 241

@saml12345 related to your first code example: have you tried using a dict instead of what appears to be a string to represent your variables?

0 Likes
Tourist
8 1 2

Hi @saml12345, I had the same problem. The issue is that GraphQL expects the body of your POST request to be different depending on the Content-Type header you set (see this part of the GraphQL docs).

 

A standard GraphQL POST request should use the application/json content type, and include a JSON-encoded body of the following form:

 

{
"query": "...",
"operationName": "...",
"variables": { "myVariable": "someValue", ... }
}

[...]

 

If the "application/graphql" Content-Type header is present, treat the HTTP POST body contents as the GraphQL query string.

So in the first example you posted, you could change the Content-Type to 'application/json' and it should just work since you're already serving a JSON encoded body.

 

Alternatively you could update your request body to be just the GraphQL string (use "data=" instead of "json=" in python requests, otherwise I think it will update the headers...) For example:

 

 

response = requests.post(
url=f'https://{shop}.myshopify.com/admin/api/2019-07/graphql.json',
data=data,
headers=headers # 'Content-Type': 'application/graphql'
)

 

However if you do this you need to include your variables as URL parameters, so your new url would be:

 

f'https://{shop}.myshopify.com/admin/api/2019-07/graphql.json?variables={"orderNum":3}'

 

Hope that helps. Good luck :)

 

Andrew.

 

1 Like