profusion / sgqlc

Simple GraphQL Client
https://sgqlc.readthedocs.io/
ISC License
513 stars 85 forks source link

http request encoding fails for some emoijs / UTF characters #150

Closed ascheucher closed 2 years ago

ascheucher commented 3 years ago

Hello,

we use the your library to interact with Shopify's API, which works great in general. Thanks for your effort to create and maintain it!

One of our use cases is to let our customers to customise products for them, we end up having a lot of emojis in the queries. Same of them cause encoding Exceptions. Like this here: 😍

POSTing it causes following error message:

2021-05-31 12:07:49,203 - ERROR - Process_28636 - MainThread - sgqlc.endpoint.http - GraphQL query failed with 1 errors
2021-05-31 12:07:49,205 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http - Error #0:
2021-05-31 12:07:49,209 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http -    | Parse error on bad Unicode escape sequence: "{\"customization_attributes\": {\"Design Id\": \"roadbik3-0073-a--nt\", \"_personalized\": \"true\", \"_personalized_value\": {\"dein Wunschtext\": \"Gravelbike \xED\xA0\xBD\xED\xB8\x8D\"}, \"dein Wunschtext\": \"Gravelbike \xED\xA0\xBD\xED\xB8\x8D\", \"Preview\": \"XXX\", \"_design_Preview\": \"XXX\", \"_pplr_preview\": \"Preview\"}}" (error) at [2, 229]
2021-05-31 12:07:49,213 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http -    -
2021-05-31 12:07:49,217 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http -    | Locations:
2021-05-31 12:07:49,222 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http -    |  0 | mutation OrderAddMetafield {
2021-05-31 12:07:49,224 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http -    |  1 | orderUpdate(input: {id: "gid://shopify/Order/XXXXXXXXXXXXX", metafields: [{description: "Customization data for LineItem(xxxxxxxxxxxxx) of Order(XXXXXXXXXXXXX).", key: "li_cust_xxxxxxxxxxxxx", namespace: "xxxxx_cstmzng", value: "{\"customization_attributes\": {\"Design Id\": \"roadbik3-0073-a--nt\", \"_personalized\": \"true\", \"_personalized_value\": {\"dein Wunschtext\": \"Gravelbike \\ud83d\\ude0d\"}, \"dein Wunschtext\": \"Gravelbike \\ud83d\\ude0d\", \"Preview\": \"xxxx\", \"_design_Preview\": \"xxxx\", \"_pplr_preview\": \"Preview\"}}", valueType: JSON_STRING}]}) {
2021-05-31 12:07:49,227 - INFO - Process_28636 - MainThread - sgqlc.endpoint.http -    |      ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------^
barbieri commented 3 years ago

I think there may be an encoding error somewhere, I got the 3rd line in the log where it says: Parse error on bad Unicode escape sequence: and printed it, there are no emoji in the output, just some bogus bytes after Gravelbike

This is a simply Python program, not sgqlc related:

s = "{\"customization_attributes\": {\"Design Id\": \"roadbik3-0073-a--nt\", \"_personalized\": \"true\", \"_personalized_value\": {\"dein Wunschtext\": \"Gravelbike \xED\xA0\xBD\xED\xB8\x8D\"}, \"dein Wunschtext\": \"Gravelbike \xED\xA0\xBD\xED\xB8\x8D\", \"Preview\": \"XXX\", \"_design_Preview\": \"XXX\", \"_pplr_preview\": \"Preview\"}}"
print(s)

I'm not sure if that was supposedly to be the 😍, because here it's something else in UTF-8:

>>> print(bytes(u'😍', 'utf-8'))
b'\xf0\x9f\x98\x8d'

In your case it's ED instead of F0, A0 instead of 9F...

barbieri commented 3 years ago

also, hint for production-ready GraphQL: avoid generating one query for each input parameters, this makes the server's life bit harder, blocks caching and so on. We still don't support it out of the box, but it would block the "Apollo Persisted Queries" to be implemented.

Prefer to use Variable('name') and then you send the variables alongside your query.

barbieri commented 3 years ago

@ascheucher ping? could you provide more info?

ascheucher commented 3 years ago

Hi! Sorry for my late reply, had been overwhelmed with other stuff. Indeed it had been an encoding problem introduced by json.dumps(dict(customization_attributes=props)), where props is a dict with different values.

Currently I try to wrap around my head the Variable concept by studying the examples/github/sample_operations.py example.

Thanks for your pointer in the right direction!

barbieri commented 3 years ago

Weird, json.dumps() should work as it should be UTF-8. Maybe it's before that?

Variables allow you to write a template query. The query stays the same (you can even save the output, avoiding converting it on every request, just bytes(your_operation) and use that instead of your_operation), then you send the variables as a dict of variable_name with their variable_value. The variable_value depends on the GraphQL Type, it may be plain objects (str, int, float, boolean...) or complex (list, dict).

ascheucher commented 3 years ago

No, not before. I can see the emoji in the debugger. Maybe afterwards then? I have to check again.

I just realised you added the current Shopify schema to the example section. That's a kind move!

I wanted to get the query with variables up and running before. Just found the sgqlc-codegen operation functionality and created a mutation. As far as I can remember, this generator was not around last time before Christmas. It's a great usability improvement for the library!

Where I still fail is handing over the list arguments. May I ask how I have to wrap the single element? I can't find an example for that. And obviously I didn't fully get hang of all the connections I guess till now.

The gql mutation:

mutation OrderAddMetafield($order_id: ID!, $metafields: [MetafieldInput!]) {
  orderUpdate(input: {id: $order_id, metafields: $metafields}) {
    order {
      id
    }
    userErrors {
      field
      message
    }
  }
}

The generated code:

def mutation_order_add_metafield():
    _op = sgqlc.operation.Operation(_schema_root.mutation_type, name='OrderAddMetafield', variables=dict(order_id=sgqlc.types.Arg(sgqlc.types.non_null(_schema.ID)), metafields=sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(_schema.MetafieldInput)))))
    _op_order_update = _op.order_update(input={'id': sgqlc.types.Variable('order_id'), 'metafields': sgqlc.types.Variable('metafields')})
    _op_order_update_order = _op_order_update.order()
    _op_order_update_order.id()
    _op_order_update_user_errors = _op_order_update.user_errors()
    _op_order_update_user_errors.field()
    _op_order_update_user_errors.message()
    return _op

I have a wrapper class which also does some throttling against the Shopify endpoint. Hence, the call looks a bit different. But finally it calls the sgqlc endpoint:

def add_order_metafield(sy_gql_client, sy_order_id, key, value, namespace="global", value_type='STRING', description=None):
    _op = sgqlc.operation.Operation(
        _schema_root.mutation_type,
        name='OrderAddMetafield',
        variables=dict(order_id=sgqlc.types.Arg(sgqlc.types.non_null(_schema.ID)),
                       metafields=sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(_schema.MetafieldInput)))))
    _op_order_update = _op.order_update(input={
        'id': sgqlc.types.Variable('order_id'), 
        'metafields': sgqlc.types.Variable('metafields')})
    _op_order_update_order = _op_order_update.order()
    _op_order_update_order.id()
    _op_order_update_user_errors = _op_order_update.user_errors()
    _op_order_update_user_errors.field()
    _op_order_update_user_errors.message()

    response = _op + sy_gql_client.execute(
        _op,
        variables=ArgDict(
            order_id=sy_order_id,
            metafields=[schema.MetafieldInput(
                namespace=namespace,
                key=key,
                value=value,
                value_type=value_type,
                description=description)]))
    if hasattr(response, 'errors'):
        raise RuntimeError(response.errors)
    return response.order_update.order

Which returns the ValueError: OrderInput selection 'metafields': $metafields ('Variable' object is not iterable)

barbieri commented 3 years ago

that bug was fixed in the last commit in the master, or should be. Could you check: https://github.com/profusion/sgqlc/commit/5be5ee1e3b048cf7af8976eb6cb7032ff56299a8

your code looks correct

ascheucher commented 3 years ago

My experience with the python module system is limited. Hence, I recap what I did to test it:

I installed the commit version with pip install git+git://github.com/profusion/sgqlc.git@5be5ee1e3b048cf7af8976eb6cb7032ff56299a8 into my project.

To give you a better picture, I updated the code blog in my last comment with the surroundings (search for: _add_ordermetafield). This are my call parameters:

sy_order_id = 'gid://shopify/Order/3730750341177'
key = 'li_cust_9789983162425'
namespace = 'otaya_cstmzng'
value_type = 'JSON_STRING'
description = 'Customization data for LineItem(9789983162425) of Order(3730750341177).'
value = {
    'customization_attributes': {
        'Design Id': 'dog-0029-a--nt', 
        '_personalized': 'true', 
        '_personalized_value': {
            'Name': '😍 Dog Name 😍'
        }, 
        'Name': '😍 Dog Name 😍', 
        '_Vorschau': 'https: //cdn.shopify.com/s/files/1/0095/5337/9385/uploads/xxxxx.png?format=png&png', 
        '_svg 0': 'https: //cdn.shopify.com/s/files/1/0095/5337/9385/uploads/xxxxx.svg', 
        '_pplr_preview': '_Vorschau'
    }
}

Then I rerun my code as shown above, but got following error:

2021-06-03 10:58:42,347 - ERROR - Process_34841 - MainThread - 04-10-order-handling - Something went wrong at order 3730750341177 / #otaya-de-dev1080
Traceback (most recent call last):
  File "/workspace/design_db/04_shopify_scripts/10-handle-orders-with-anomalies.py", line 219, in check_order
    check_customizations(sy_gql_client, order)
  File "/workspace/design_db/04_shopify_scripts/10-handle-orders-with-anomalies.py", line 291, in check_customizations
    store_properties_as_metafield(
  File "/workspace/design_db/04_shopify_scripts/10-handle-orders-with-anomalies.py", line 336, in store_properties_as_metafield
    sy_commons.add_order_metafield(
  File "/workspace/design_db/modules/otaya/shopify/commons.py", line 1163, in add_order_metafield
    variables=ArgDict(
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 2321, in __init__
    v = Arg(v)
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 2167, in __init__
    super(Arg, self).__init__(typ, graphql_name)
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 2013, in __init__
    self._type = BaseType.__ensure__(typ)
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 904, in __ensure__
    return map_python_to_graphql[t]
TypeError: unhashable type: 'list'

I also tried to recreate the code with sgqlc-code operation ..., but this did not change the created code. I assume your fix is purely related to the execution and applying the variables.

Can I do something else to test it?

ascheucher commented 3 years ago

Just to be sure it's not related to my Shopify GraphQL client code, I took your Shopify example code and added my mutation. I also added a simple query to see whether this works. It does and shows the expected result.

It seems there is still a bug somewhere.

Code:

import logging
import yaml
import sgqlc
import sgqlc.types
import sgqlc.operation
from sgqlc.endpoint.http import HTTPEndpoint
from otaya.shopify.sgql_client import SyGqlClient, SyGraphQlInternalError
import otaya.shopify.sgql.v2020_10.schema as schema

_schema = schema
_schema_root = _schema.schema

__all__ = ('Operations',)

def mutation_order_add_metafield():
    _op = sgqlc.operation.Operation(_schema_root.mutation_type, name='OrderAddMetafield', variables=dict(order_id=sgqlc.types.Arg(
        sgqlc.types.non_null(_schema.ID)), metafields=sgqlc.types.Arg(sgqlc.types.list_of(sgqlc.types.non_null(_schema.MetafieldInput)))))
    _op_order_update = _op.order_update(input={'id': sgqlc.types.Variable(
        'order_id'), 'metafields': sgqlc.types.Variable('metafields')})
    _op_order_update_order = _op_order_update.order()
    _op_order_update_order.id()
    _op_order_update_user_errors = _op_order_update.user_errors()
    _op_order_update_user_errors.field()
    _op_order_update_user_errors.message()
    return _op

def query_order_name_with_metafields():
    _op = sgqlc.operation.Operation(_schema_root.query_type, name='OrderNameWithMetafields', variables=dict(
        order_id=sgqlc.types.Arg(sgqlc.types.non_null(_schema.ID))))
    _op_order = _op.order(id=sgqlc.types.Variable('order_id'))
    _op_order.id()
    _op_order.name()
    _op_order_metafields = _op_order.metafields(first=25)
    _op_order_metafields_edges = _op_order_metafields.edges()
    _op_order_metafields_edges_node = _op_order_metafields_edges.node()
    _op_order_metafields_edges_node.id()
    _op_order_metafields_edges_node.key()
    _op_order_metafields_edges_node.value()
    return _op

class Mutation:
    order_add_metafield = mutation_order_add_metafield()

class Query:
    order_name_with_metafields = query_order_name_with_metafields()

class Operations:
    mutation = Mutation
    query = Query

def main():
    endpoint_loglevel = 30
    logfmt = '%(levelname)s: %(message)s'
    if endpoint_loglevel < logging.ERROR:
        logfmt = '%(levelname)s:%(name)s: %(message)s'

    logging.basicConfig(format=logfmt, level=endpoint_loglevel)
    HTTPEndpoint.logger.setLevel(endpoint_loglevel)

    config = yaml.safe_load(open(r"design_db/config.yml"))
    token = config['config']['shopify']['password']

    url = 'https://otaya-de-dev.myshopify.com/admin/api/2021-04/graphql.json'
    endpoint = HTTPEndpoint(url, {
        "Content-Type": "application/json",
        'X-Shopify-Access-Token': token
    })

    order_id = 'gid://shopify/Order/3730750341177'

    q = query_order_name_with_metafields()
    order = q + endpoint(q, variables=dict(orderId=order_id))

    print(order)

    op = mutation_order_add_metafield()

    key = 'li_cust_9789983162425',
    namespace = 'otaya_cstmzng',
    value_type = 'JSON_STRING',
    description = 'Customization data for LineItem(9789983162425) of Order(3730750341177).',
    value = {
        'customization_attributes': {
            'Design Id': 'dog-0029-a--nt',
            '_personalized': 'true',
            '_personalized_value': {
                'Name': '😍 Dog Name 😍'
            },
            'Name': '😍 Dog Name 😍',
            '_Vorschau': 'https: //cdn.shopify.com/s/files/1/0095/5337/9385/uploads/xxxxx.png?format=png&png',
            '_svg 0': 'https: //cdn.shopify.com/s/files/1/0095/5337/9385/uploads/xxxxx.svg',
            '_pplr_preview': '_Vorschau'
        }
    }

    result = op + endpoint(op, varibales=sgqlc.types.ArgDict(
            order_id=order_id,
            metafields=[schema.MetafieldInput(
                namespace=namespace,
                key=key,
                value=value,
                value_type=schema.MetafieldValueType('JSON_STRING'),
                description=description)]))

    print(result)

if __name__ == "__main__":
    main()

The resulting stacktrace:

vscode ➜ /workspace (develop βœ—) $  cd /workspace ; /usr/bin/env /usr/local/bin/python /home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/launcher 41689 -- /workspace/tinker_around/sgqlc_list_test.py 
QueryRoot(order=Order(id=gid://shopify/Order/3730750341177, name=#otaya-de-dev1080, metafields=MetafieldConnection(edges=[])))
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/__main__.py", line 45, in <module>
    cli.main()
  File "/home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 444, in main
    run()
  File "/home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 285, in run_file
    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
  File "/usr/local/lib/python3.9/runpy.py", line 268, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "/usr/local/lib/python3.9/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/local/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/workspace/tinker_around/sgqlc_list_test.py", line 115, in <module>
    main()
  File "/workspace/tinker_around/sgqlc_list_test.py", line 102, in main
    result = op + endpoint(op, varibales=sgqlc.types.ArgDict(
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 2321, in __init__
    v = Arg(v)
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 2167, in __init__
    super(Arg, self).__init__(typ, graphql_name)
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 2013, in __init__
    self._type = BaseType.__ensure__(typ)
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/types/__init__.py", line 904, in __ensure__
    return map_python_to_graphql[t]
TypeError: unhashable type: 'list'
vscode ➜ /workspace (develop βœ—) $ 

Thanks for taking a look!

barbieri commented 3 years ago

I'll take a look later today or tomorrow, but there is a typo:

    result = op + endpoint(op, varibales=sgqlc.types.ArgDict(

variables is misspelled, then it's being passed as a variable itself.

But variables must be plain python types, or something json.dump() will handle.

But the TypeError hints at the bug, you're using ArgDict that is used to specify the type, it maps the argument name to its type, to pass a value. If you can look before I do, try the code below:

result = op + endpoint(op, variables=dict(
            order_id=order_id,
            metafields=[dict(
                namespace=namespace,
                key=key,
                value=value,
                value_type='JSON_STRING',
                description=description)]))

Variables are plain JSON objects

ascheucher commented 3 years ago

Thanks for pointing out the typo!

I needed to convert the dict keys to camelCase, but then I got one step further:

    result = op + endpoint(op, variables=dict(
            orderId=order_id,
            metafields=[dict(
                namespace=namespace,
                key=key,
                value=value,
                valueType=value_type,
                description=description)]))

but then I get following error:

sgqlc.operation.GraphQLErrors: 
Variable $metafields of type [MetafieldInput!] was provided invalid value for 
0.namespace (Could not coerce value ["otaya_cstmzng"] to String), 
0.key (Could not coerce value ["li_cust_9789983162425"] to String), 
0.value (Could not coerce value "{\"customization_attributes\"=>{\"Design Id\"=>\"dog-0029-a--nt\", \"_personalized\"=>\"true\", \"_personalized_value\"=>{\"Name\"=>\"😍 Dog Name 😍\"}, \"Name\"=>\"😍 Dog Name 😍\", \"_Vorschau\"=>\"https: \/\/cdn.shopify.com\/s\/files\/1\/0095\/5337\/9385\/uploads\/xxxxx.png?format=png&png\", \"_svg 0\"=>\"https: \/\/cdn.shopify.com\/s\/files\/1\/0095\/5337\/9385\/uploads\/xxxxx.svg\", \"_pplr_preview\"=>\"_Vorschau\"}}" to String), 
0.valueType (Expected ["JSON_STRING"] to be one of: STRING, INTEGER, JSON_STRING), 
0.description (Could not coerce value ["Customization data for LineItem(9789983162425) of Order(3730750341177)."] to String)

It seems to treat all string values as single element list of strings.

Changing the variable definition to work without the dict() function delivers the same error:

result = op + endpoint(op, variables={
        "orderId": order_id,
        "metafields": [{
            "namespace": namespace,
            "key": key,
            "value": value,
            "valueType": value_type,
            "description": description}]
    })

For completeness the whole unformatted stacktrace:

vscode ➜ /workspace (develop βœ—) $  cd /workspace ; /usr/bin/env /usr/local/bin/python /home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/launcher 37275 -- /workspace/tinker_around/sgqlc_list_test.py 
QueryRoot(order=Order(id=gid://shopify/Order/3730750341177, name=#otaya-de-dev1080, metafields=MetafieldConnection(edges=[])))
ERROR:sgqlc.endpoint.http: GraphQL query failed with 1 errors
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/__main__.py", line 45, in <module>
    cli.main()
  File "/home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 444, in main
    run()
  File "/home/vscode/.vscode-server/extensions/ms-python.python-2021.5.842923320/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 285, in run_file
    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
  File "/usr/local/lib/python3.9/runpy.py", line 268, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "/usr/local/lib/python3.9/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/local/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/workspace/tinker_around/sgqlc_list_test.py", line 115, in <module>
    main()
  File "/workspace/tinker_around/sgqlc_list_test.py", line 102, in main
    result = op + endpoint(op, variables={
  File "/home/vscode/.local/lib/python3.9/site-packages/sgqlc/operation/__init__.py", line 2155, in __add__
    raise ex
sgqlc.operation.GraphQLErrors: Variable $metafields of type [MetafieldInput!] was provided invalid value for 0.namespace (Could not coerce value ["otaya_cstmzng"] to String), 0.key (Could not coerce value ["li_cust_9789983162425"] to String), 0.value (Could not coerce value "{\"customization_attributes\"=>{\"Design Id\"=>\"dog-0029-a--nt\", \"_personalized\"=>\"true\", \"_personalized_value\"=>{\"Name\"=>\"😍 Dog Name 😍\"}, \"Name\"=>\"😍 Dog Name 😍\", \"_Vorschau\"=>\"https: \/\/cdn.shopify.com\/s\/files\/1\/0095\/5337\/9385\/uploads\/xxxxx.png?format=png&png\", \"_svg 0\"=>\"https: \/\/cdn.shopify.com\/s\/files\/1\/0095\/5337\/9385\/uploads\/xxxxx.svg\", \"_pplr_preview\"=>\"_Vorschau\"}}" to String), 0.valueType (Expected ["JSON_STRING"] to be one of: STRING, INTEGER, JSON_STRING), 0.description (Could not coerce value ["Customization data for LineItem(9789983162425) of Order(3730750341177)."] to String)
vscode ➜ /workspace (develop βœ—) $ 
barbieri commented 3 years ago

Hi, sorry taking so long, busy work week.

As for the case, yes, the variable names must be in the exact case, we do no conversion.

However the error message is a bit strange:

0.namespace (Could not coerce value ["otaya_cstmzng"] to String), 
0.key (Could not coerce value ["li_cust_9789983162425"] to String), 
0.value (Could not coerce value "{\"customization_attributes\"=>{\"Design Id\"=>\"dog-0029-a--nt\", \"_personalized\"=>\"true\", \"_personalized_value\"=>{\"Name\"=>\"😍 Dog Name 😍\"}, \"Name\"=>\"😍 Dog Name 😍\", \"_Vorschau\"=>\"https: \/\/cdn.shopify.com\/s\/files\/1\/0095\/5337\/9385\/uploads\/xxxxx.png?format=png&png\", \"_svg 0\"=>\"https: \/\/cdn.shopify.com\/s\/files\/1\/0095\/5337\/9385\/uploads\/xxxxx.svg\", \"_pplr_preview\"=>\"_Vorschau\"}}" to String), 
0.valueType (Expected ["JSON_STRING"] to be one of: STRING, INTEGER, JSON_STRING), 
0.description (Could not coerce value ["Customization data for LineItem(9789983162425) of Order(3730750341177)."] to String)

Seems the server is receiving lists instead of plain elements for each list item. These errors are generated by the server, it looks like the server is receiving the following:

{
        "orderId": order_id,
        "metafields": [{
            "namespace": [namespace],
            "key": [key],
            "value": [value],
            "valueType": [value_type],
            "description": [description]}]
    }

Instead of:

{
        "orderId": order_id,
        "metafields": [{
            "namespace": namespace,
            "key": key,
            "value": value,
            "valueType": value_type,
            "description": description}]
    }

Ah, and I get why it's like that, just noticed as I was writing this comment. Take a look at the trailing , in your lines! They generate TUPLES! πŸ˜…

    key = 'li_cust_9789983162425',
    namespace = 'otaya_cstmzng',
    value_type = 'JSON_STRING',
    description = 'Customization data for LineItem(9789983162425) of Order(3730750341177).',
    value = {
        'customization_attributes': {
            'Design Id': 'dog-0029-a--nt',
            '_personalized': 'true',
            '_personalized_value': {
                'Name': '😍 Dog Name 😍'
            },
            'Name': '😍 Dog Name 😍',
            '_Vorschau': 'https: //cdn.shopify.com/s/files/1/0095/5337/9385/uploads/xxxxx.png?format=png&png',
            '_svg 0': 'https: //cdn.shopify.com/s/files/1/0095/5337/9385/uploads/xxxxx.svg',
            '_pplr_preview': '_Vorschau'
        }
    }
ascheucher commented 3 years ago

Oh damn! 🀦 Sorry for the mess. Feel a bit stupid now. Removed the commas did the trick! I am pretty sure I had used a dict() before and fiddling around I converted it to a {} dict and forgot to remove the commas...

My original problem

And finally also figured out how to correctly prepare a string containing UTF8 emojis to be properly serialized and stored. I load a UTF8 string with json.loads(...), add these objects to a dictionary which is later on converted to a string again. I used to do it json.dumps(value), which returns ASCII!

Everything in Python3 is UFT8 now, except this function obviously to keep it backwards compatible. Using json.dumps(value, ensure_ascii=False) did solve the encoding issue. This returns UTF8. 🀷

Your bugfix

Regarding your fix. Do you know already, when you release a new version including it?

Documentation for slightly more complex queries and mutations

And many thanks for helping me jumping through the Variables hoops! Like the outcome much more now.

The documentation had been a bit steep for me. Maybe you could include something a bit more complex like this example in the Shopify introduction examples:

    result = op + endpoint(op, variables=dict(
            orderId=order_id,
            metafields=[dict(
                namespace=namespace,
                key=key,
                value=value,
                valueType=value_type,
                description=description)]))

I am pretty sure I am not alone struggling with this. There are real a lot of little details to get right to hand over the values for the Variables.

If you want me to do it, I am happy to create a pull request.

barbieri commented 3 years ago

Sure, if you can create examples (one or more) in the examples/shopify, I'll be glad to add them in addition to the existing list_products.py. If you have the time and could add some text alongside them in the form of a README.md, it would be super nice for me to point out to users.

I have a dev account in shopify so I can test my example. If I need to prepare some special items/features, let me know what to do, ok?

I'll release another version later today, I'll let you know

barbieri commented 3 years ago

sorry taking so long, but it's done: https://pypi.org/project/sgqlc/14.0/

barbieri commented 2 years ago

done in v14

ascheucher commented 2 years ago

Sorry for not delivering the Shopify examples. Live happened. :)

Thanks for the fix!