graphql-python / gql

A GraphQL client in Python
https://gql.readthedocs.io
MIT License
1.53k stars 179 forks source link

Shopify error: Prefix "query" when sending a mutation #471

Closed mattroberts96 closed 6 months ago

mattroberts96 commented 6 months ago

When I have my mutation written I get an error when the request is sent that there is an unexpected sting (\"query\") at the start of my code. when the response is sent i can see that there is a hardcoded prefix "query" at the start of the request. I am unsure what to do now as I dont have that hard coded in my mutation so any advice on what to do would be greatly appreciated. There might be something I am doing wrong but I am just unsure. Thanks. I have transport and a client already defined in my code so not sure if there is an error in that but its not relevant to my traceback error as well which is the same as log errors.

2024-02-28 10:05:26 INFO: >>> {"query": "mutation customerRequestDataErasure($customerId: ID!) {\n customerRequestDataErasure(customerId: $customerId) {\n customerId\n userErrors {\n field\n message\n code\n }\n }\n}", "variables": {"customerId": "global_id (removed for security purposes)"}} 2024-02-28 10:05:26 INFO: <<< {"errors":[{"message":"syntax error, unexpected STRING (\"query\") at [1, 2]","locations":[{"line":1,"column":2}]}]}

My mutation:

    query = gql("""
            mutation customerRequestDataErasure($customerId: ID!) {
                customerRequestDataErasure(customerId: $customerId) {
                    customerId
                    userErrors {
                        field
                        message
                        code
                    }
                }
            }
            """)

    params = {"customerId": ""}

    result = client.execute(query, variable_values=params)
leszekhanusz commented 6 months ago

Sending a json with a query key is perfectly normal

mattroberts96 commented 6 months ago

I'm using python as my backend and my url is using a graphql endpoint. it isi using shopify graphql endpoint.

leszekhanusz commented 6 months ago

Alright,

It seems the problem is that Shopify does not provide a standard GraphQL HTTP interface.

The documentation for the GraphQL API is here

And a GraphiQl interface of a demo store is available at https://shopify.dev/graphiql/admin-graphiql

Reverse engineering (with chrome dev-tools, copy as curl) what is sent on this graphiql interface shows a request looking like this:

curl 'https://shopify.dev/admin-graphql-proxy' \
  -H 'authority: shopify.dev' \
  -H 'accept: */*' \
  -H 'accept-language: en-US,en;q=0.9,la;q=0.8,fr;q=0.7,nl;q=0.6' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'cookie: shopify_experiment_assignments=%5B%5D; _shopify_dev_session=REDACTED' \
  -H 'origin: https://shopify.dev' \
  -H 'pragma: no-cache' \
  -H 'referer: https://shopify.dev/graphiql/admin-graphiql' \
  -H 'sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "Linux"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: same-origin' \
  -H 'user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36' \
  -H 'x-csrf-token: REDACTED' \
  --data-raw '{"graphQLParams":{"query":"{\n  shop {\n    name\n  }\n}","variables":null},"version":"2024-01"}'

What is strange in that request is that you can see a graphQLParams key in the request instead of directly the query key. This does not seem to be documented in the Shopify GraphQL documentation.

I was able to execute a query using gql using a modified transport for Shopify which will modify the request to put it inside the GraphQLParams key. The following code should be executable:

import logging
logging.basicConfig(level=logging.INFO)

from gql import gql, Client
from gql.transport.httpx import HTTPXTransport

class ShopifyTransport(HTTPXTransport):

    def _prepare_request(
        self,
        document,
        variable_values = None,
        operation_name = None,
        extra_args = None,
        upload_files = False,
    ):

        payload = super()._prepare_request(
            document=document,
            variable_values=variable_values,
            operation_name=operation_name,
            extra_args=extra_args,
            upload_files=upload_files,
        )

        shopify_payload = {
            "graphQLParams": payload["json"],
            "version": "2024-01",
        }

        return {"json": shopify_payload}

transport = ShopifyTransport(url="https://shopify.dev/admin-graphql-proxy")
client = Client(transport=transport)

query = gql("""
{
  shop {
    name
  }
}
""")

result = client.execute(query)

print (f"result = {result}")

You could try using that ShopifyTransport above on your own shop to see if it solves your problem.