使用GraphQL机制,但返回CSV

hyrbngr7  于 2023-05-04  发布在  其他
关注(0)|答案(3)|浏览(145)

一个普通的REST API可能允许你以不同的格式请求相同的数据,使用不同的Accept头,例如application/jsontext/htmltext/csv的响应。
然而,如果你使用GraphQL,JSON似乎是唯一可接受的返回内容类型。但是,我需要我的API能够返回CSV数据,以供不太复杂的客户机使用,这些客户机不理解JSON。
如果给定一个Accept: text/csv头,GraphQL端点返回CSV数据是否有意义?如果没有,有没有更好的方法来做到这一点?
这更像是一个概念性的问题,但我专门使用Graphene来实现我的API。它是否提供了任何处理自定义内容类型的机制?

cs7cruho

cs7cruho1#

是的,你可以,但它不是内置的,你必须覆盖一些东西。这更像是一个工作。
执行以下步骤,您将获得csv输出:
1.将csv = graphene.String()添加到您的查询中,并将其解析为您想要的任何内容。
1.创建一个继承GraphQLView的新类
1.重写dispatch函数,使其看起来像这样:

def dispatch(self, request, *args, **kwargs):
    response = super(CustomGraphqlView, self).dispatch(request, *args, **kwargs)
    try:
        data = json.loads(response.content.decode('utf-8'))
        if 'csv' in data['data']:
            data['data'].pop('csv')
            if len(list(data['data'].keys())) == 1:
                model = list(data['data'].keys())[0]
            else:
                raise GraphQLError("can not export to csv")
            data = pd.json_normalize(data['data'][model])
            response = HttpResponse(content_type='text/csv')
            response['Content-Disposition'] = 'attachment; filename="output.csv"'

            writer = csv.writer(response)
            writer.writerow(data.columns)
            for value in data.values:
                writer.writerow(value)
    except GraphQLError as e:
        raise e
    except Exception:
        pass
    return response

1.导入所有必需的模块
1.用新的视图类替换urls.py文件中的默认GraphQLView
现在,如果你在GraphQL查询中包含“csv”,它将返回原始csv数据,然后你可以将数据保存到前端的csv文件中。示例查询如下所示:

query{
  items{
    id
    name
    price
    category{
        name
    }
  }
  csv
}

请记住,这是一种以csv格式获取原始数据的方法,您必须保存它。你可以在JavaScript中使用以下代码来实现:

req.then(data => {
    let element = document.createElement('a');
    element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(data.data));
    element.setAttribute('download', 'output.csv');

    element.style.display = 'none';
    document.body.appendChild(element);

    element.click();

    document.body.removeChild(element);
})

这种方法会展平JSON数据,因此不会丢失任何数据。

iqih9akk

iqih9akk2#

我必须实现将列表查询导出到CSV文件的功能。下面是我如何实现扩展@Sina方法的。
我的graphql查询检索用户列表(限制分页)是

query userCsv{
    userCsv{
        csv
        totalCount
        results(limit: 50, offset: 50){
            id
            username
            email
            userType
        }
    }
}

通过从GraphQLView继承来创建CustomGraphQLView视图,并覆盖调度函数,以查看查询是否具有csv,并确保更新指向此自定义GraphQLView的graphql url。

class CustomGraphQLView(GraphQLView):
    def dispatch(self, request, *args, **kwargs):
        try:
            query_data = super().parse_body(request)
            operation_name = query_data["operationName"]
        except:
            operation_name = None
        response = super().dispatch(request, *args, **kwargs)
        csv_made = False
        try:
            data = json.loads(response.content.decode('utf-8'))
            try:
                csv_query = data['data'][f"{operation_name}"]['csv']
                csv_query = True
            except:
                csv_query = None
            if csv_query:
                csv_path = f"{settings.MEDIA_ROOT}/csv_{datetime.now()}.csv"
                results = data['data'][f"{operation_name}"]['results']
                # header = results[0].keys()
                results = json_normalize(results)
                results.to_csv(csv_path, index=False)
                data['data'][f"{operation_name}"]['csv'] = csv_path
                csv_made = True
        except GraphQLError as e:
            raise e
        except Exception:
            pass
        if csv_made:
            return HttpResponse(
                status=200, content=json.dumps(data), content_type="application/json"
        )
        return response

操作名称是调用所用的查询名称。在前面的示例中,它是userCsv,并且它是必需的,因为作为响应的最终结果带有此键。获得的响应是django http响应对象。使用上面的操作名称,我们检查在查询中是否存在CSV,如果不存在,则按原样返回响应,但是如果存在CSV,则提取查询结果,并且制作CSV文件,并且存储它,并且在响应中附加它的路径。
下面是查询的graphql模式

class UserListCsvType(DjangoListObjectType):
    csv = graphene.String()
    class Meta:
        model = User
        pagination = LimitOffsetGraphqlPagination(default_limit=25, ordering="-id")

class DjangoListObjectFieldUserCsv(DjangoListObjectField):
    @login_required
    def list_resolver(self, manager, filterset_class, filtering_args, root, info, **kwargs):
        return super().list_resolver(manager, filterset_class, filtering_args, root, info, **kwargs)

class Query(graphene.ObjectType):
    user_csv = DjangoListObjectFieldUserCsv(UserListCsvType)

下面是示例响应

{
  "data": {
    "userCsv": {
      "csv": "/home/shishir/Desktop/sample-project/media/csv_2021-11-22 15:01:11.197428.csv",
      "totalCount": 101,
      "results": [
        {
          "id": "51",
          "username": "kathryn",
          "email": "candaceallison@gmail.com",
          "userType": "GUEST"
        },
        {
          "id": "50",
          "username": "bridget",
          "email": "hsmith@hotmail.com",
          "userType": "GUEST"
        },
        {
          "id": "49",
          "username": "april",
          "email": "hoffmanzoe@yahoo.com",
          "userType": "GUEST"
        },
        {
          "id": "48",
          "username": "antonio",
          "email": "laurahall@hotmail.com",
          "userType": "PARTNER"
        }
      ]
    }
  }
}

PS:上面生成的数据来自faker库,我使用的是graphene-django-extras,json_normalize来自pandas。CSV文件可以从响应中获得的路径下载。

jdgnovmf

jdgnovmf3#

GraphQL依赖于(并且因为)响应嵌套数据而发光。据我所知,CSV只能显示平面键值对。这使得CSV并不真正适合GraphQL响应。
我认为实现你想要做的事情的最干净的方法是在你的客户端前面放一个GraphQL客户端:

+------+  csv  +-------+  http/json  +------+
|client|<----->|adapter|<----------->|server|
+------+       +-------+             +------+

这里的好处是,您的适配器只需要能够将它指定的查询转换为CSV。
显然,你可能并不总是能够做到这一点(但你如何让他们发送GraphQL查询)。或者,您可以构建一个将JSON转换为CSV的中间件。但是你必须处理整个GraphQL规范。祝你好运翻译这个答案:

{
  "__typename": "Query",
  "someUnion": [
    { "__typename": "UnionA", "numberField": 1, "nested": [1, 2, 3, 4] },
    { "__typename": "UnionB", "stringField": "str" },
  ],
  "otherField": 123.34
}

因此,如果你不能通过HTTP传输CSV,那么GraphQL就是一个错误的选择,因为它不是为这个而构建的。如果你不允许那些很难转换成CSV的GraphQL特性,你就不再有GraphQL了,所以叫它GraphQL也没有意义。

相关问题