GraphQL 介绍及 Python 实现 -- 知识铺
微信公众号:虎珀
感谢关注。问题或建议,请公众号留言
目录
什么是 GraphQLGraphQL Schema 重试 错误原因 GraphQL SDL 重试 错误原因 模式 schema对象 type方法 queryResolver 解析器代码 Python 实现测试客户端GraphQL 优点GraphQL 缺点附加成本过度查询安全问题参考每日一句
什么是 GraphQL
GraphQL 是一种用于 API 的查询语言。根据预先定义的 Schema 对数据执行查询。
GraphQL 不绑定任何特定的数据库或存储引擎。仅在现有数据的基础上,额外增加查询、过滤数据的功能。
GraphQL 最大的特点就是 “需要什么,得到什么。”
上图,左侧为查询,右侧为响应。
随着查询字段变化,响应随之更改。
这极大提高了开发效率,客户端可以按需获取数据并且服务端不用修改代码。
GraphQL Schema 重试 错误原因
GraphQL Schema 是类型、字段、方法的集合。
它们构成了我们可以在 GraphQL server 中对数据进行的所有操作。
GraphQL 有自己的语言(GraphQL Schema Definition Language),简称 SDL。 重试 错误原因
SDL 用于编写 Schema。
Schema 包含
-
定义数据类型,包含请求参数、返回值
-
定义功能函数
下面来看如何定义
GraphQL SDL 重试 错误原因
SDL 是 GraphQL Schema 自己的语言,称为模式定义语言。
让我们看一个完整的 Schema SDL 例子
schema.graphql
文件如下
<span> 1</span><span>schema</span> {<br><span> 2</span> <span>query</span>: Query<br><span> 3</span>}<br><span> 4</span><br><span> 5</span><span>type</span> <span>Post</span> {<br><span> 6</span> <span>id</span>: ID!<br><span> 7</span> title: String!<br><span> 8</span> description: String!<br><span> 9</span> created_at: String!<br><span>10</span>}<br><span>11</span><br><span>12</span><span>type</span> <span>PostsResult</span> {<br><span>13</span> <span>success</span>: Boolean!<br><span>14</span> errors: [String]<br><span>15</span> posts: [Post]<br><span>16</span>}<br><span>17</span><br><span>18</span><span>type</span> <span>Query</span> {<br><span>19</span> <span>listPosts</span>: PostsResult!<br><span>20</span>}<br>
模式 schema
<span>1</span><span>schema</span> {<br><span>2</span> <span>query</span>: Query<br><span>3</span>}<br>
schema: query 为查询,Query 为方法集合对象。GraphQL 有三种模式
-
query: 查询数据
-
mutation: 修改数据
-
subscription: 订阅数据
对象 type
<span> 1</span><span>type</span> <span>Post</span> {<br><span> 2</span> <span>id</span>: ID!<br><span> 3</span> title: String!<br><span> 4</span> description: String!<br><span> 5</span> created_at: String!<br><span> 6</span>}<br><span> 7</span><br><span> 8</span><span>type</span> <span>PostsResult</span> {<br><span> 9</span> <span>success</span>: Boolean!<br><span>10</span> errors: [String]<br><span>11</span> posts: [Post]<br><span>12</span>}<br>
type Post 定义 Post 对象,有 4 个字段。id、title、description、created_at 重试 错误原因
type PostsResult 定义 PostsResult 对象,内部嵌套了 Post 对象。
GraphQL 中可用的标量类型有 String 、 Int 、 Float 、 Boolean 和 ID 。
这些类似于任何编程语言中的标量类型。方括号( [] )表示 List 类型。
方法 query
<span>1</span><span>type</span> <span>Query</span> {<br><span>2</span> <span>listPosts</span>: PostsResult!<br><span>3</span>}<br>
type Query 定义 query 方法
listPosts:定义 listPosts 方法,无参,返回值为 PostsResult。
类型后用感叹号(!)表示必需字段(非空字段)。
Resolver 解析器
在 GraphQL 中, Resolver 是用于处理 GraphQL 查询的函数。
Resolver 用于连接 GraphQL Schema 和真实的数据源,并根据查询请求返回相应的数据。
Resolver 可以是同步的,也可以是异步的,以支持各种数据源和数据处理操作,例如数据库查询、 RPC 调用等。
以 python 代码为例,我们定义了 listPosts,无额外参数,返回 PostsResult。
<span> 1</span><span><span>def</span> <span>list_posts_resolver</span><span>(obj, info)</span>:</span><br><span> 2</span> <span>try</span>:<br><span> 3</span> posts = [post.to_dict() <span>for</span> post <span>in</span> Post.query.all()]<br><span> 4</span> payload = {<br><span> 5</span> <span>"success"</span>: <span>True</span>,<br><span> 6</span> <span>"posts"</span>: posts<br><span> 7</span> }<br><span> 8</span> <span>except</span> Exception <span>as</span> error:<br><span> 9</span> payload = {<br><span>10</span> <span>"success"</span>: <span>False</span>,<br><span>11</span> <span>"errors"</span>: [str(error)]<br><span>12</span> }<br><span>13</span> <span>return</span> payload<br>
代码 Python 实现
以下为 Python 使用 GraphQL 完整示例。
代码依赖
-
flask: web server 重试 错误原因
-
flask_cors: 跨域 重试 错误原因
-
flask_sqlalchemy: 数据库访问
-
ariadne: GraphQL 框架 重试 错误原因
安装
<span>1</span>pip install flask ariadne flask-sqlalchemy flask-cors<br>
<span> 1</span><span>from</span> datetime <span>import</span> date<br><span> 2</span><span>from</span> flask <span>import</span> Flask<br><span> 3</span><span>from</span> flask_cors <span>import</span> CORS<br><span> 4</span><span>from</span> flask_sqlalchemy <span>import</span> SQLAlchemy<br><span> 5</span><span>from</span> ariadne <span>import</span> load_schema_from_path, make_executable_schema, \<br><span> 6</span> graphql_sync, snake_case_fallback_resolvers, ObjectType, convert_kwargs_to_snake_case<br><span> 7</span><span>from</span> flask <span>import</span> request, jsonify<br><span> 8</span><br><span> 9</span>app = Flask(__name__)<br><span> 10</span>CORS(app)<br><span> 11</span><br><span> 12</span><span># 数据源为 MySQL,db 配置需修改</span><br><span> 13</span>app.config[<span>"SQLALCHEMY_DATABASE_URI"</span>] = <span>"mysql+pymysql://root:passwd@localhost/graphql"</span><br><span> 14</span>app.config[<span>"SQLALCHEMY_TRACK_MODIFICATIONS"</span>] = <span>False</span><br><span> 15</span>db = SQLAlchemy(app)<br><span> 16</span><br><span> 17</span><span># 定义 db 数据模型</span><br><span> 18</span><span><span>class</span> <span>Post</span><span>(db.Model)</span>:</span><br><span> 19</span> id = db.Column(db.Integer, primary_key=<span>True</span>)<br><span> 20</span> title = db.Column(db.String(<span>100</span>))<br><span> 21</span> description = db.Column(db.String(<span>1000</span>))<br><span> 22</span> created_at = db.Column(db.Date)<br><span> 23</span><br><span> 24</span> <span><span>def</span> <span>to_dict</span><span>(self)</span>:</span><br><span> 25</span> <span>return</span> {<br><span> 26</span> <span>"id"</span>: self.id,<br><span> 27</span> <span>"title"</span>: self.title,<br><span> 28</span> <span>"description"</span>: self.description,<br><span> 29</span> <span>"created_at"</span>: str(self.created_at.strftime(<span>'%d-%m-%Y'</span>))<br><span> 30</span> }<br><span> 31</span><br><span> 32</span><br><span> 33</span><span># 定义多个 resolver,数据源为 MySQL</span><br><span> 34</span><span><span>def</span> <span>list_posts_resolver</span><span>(obj, info)</span>:</span><br><span> 35</span> <span>try</span>:<br><span> 36</span> posts = [post.to_dict() <span>for</span> post <span>in</span> Post.query.all()]<br><span> 37</span> payload = {<br><span> 38</span> <span>"success"</span>: <span>True</span>,<br><span> 39</span> <span>"posts"</span>: posts<br><span> 40</span> }<br><span> 41</span> <span>except</span> Exception <span>as</span> error:<br><span> 42</span> payload = {<br><span> 43</span> <span>"success"</span>: <span>False</span>,<br><span> 44</span> <span>"errors"</span>: [str(error)]<br><span> 45</span> }<br><span> 46</span> <span>return</span> payload<br><span> 47</span><br><span> 48</span><br><span> 49</span><span>@convert_kwargs_to_snake_case</span><br><span> 50</span><span><span>def</span> <span>get_post_resolver</span><span>(obj, info, id)</span>:</span><br><span> 51</span> <span>try</span>:<br><span> 52</span> post = Post.query.get(id)<br><span> 53</span> payload = {<br><span> 54</span> <span>"success"</span>: <span>True</span>,<br><span> 55</span> <span>"post"</span>: post.to_dict()<br><span> 56</span> }<br><span> 57</span> <span>except</span> AttributeError: <span># not found</span><br><span> 58</span> payload = {<br><span> 59</span> <span>"success"</span>: <span>False</span>,<br><span> 60</span> <span>"errors"</span>: [<span>"Post item matching {id} not found"</span>]<br><span> 61</span> }<br><span> 62</span> <span>return</span> payload<br><span> 63</span><br><span> 64</span><br><span> 65</span><span>@convert_kwargs_to_snake_case</span><br><span> 66</span><span><span>def</span> <span>create_post_resolver</span><span>(obj, info, title, description)</span>:</span><br><span> 67</span> <span>try</span>:<br><span> 68</span> today = date.today()<br><span> 69</span> post = Post(<br><span> 70</span> title=title, description=description, created_at=today.strftime(<span>"%Y-%m-%d"</span>)<br><span> 71</span> )<br><span> 72</span> db.session.add(post)<br><span> 73</span> db.session.commit()<br><span> 74</span> payload = {<br><span> 75</span> <span>"success"</span>: <span>True</span>,<br><span> 76</span> <span>"post"</span>: post.to_dict()<br><span> 77</span> }<br><span> 78</span> <span>except</span> ValueError: <span># date format errors</span><br><span> 79</span> payload = {<br><span> 80</span> <span>"success"</span>: <span>False</span>,<br><span> 81</span> <span>"errors"</span>: [<span>f"Incorrect date format provided. Date should be in "</span><br><span> 82</span> <span>f"the format dd-mm-yyyy"</span>]<br><span> 83</span> }<br><span> 84</span> <span>return</span> payload<br><span> 85</span><br><span> 86</span><br><span> 87</span><span>@convert_kwargs_to_snake_case</span><br><span> 88</span><span><span>def</span> <span>update_post_resolver</span><span>(obj, info, id, title, description)</span>:</span><br><span> 89</span> <span>try</span>:<br><span> 90</span> post = Post.query.get(id)<br><span> 91</span> <span>if</span> post:<br><span> 92</span> post.title = title<br><span> 93</span> post.description = description<br><span> 94</span> db.session.add(post)<br><span> 95</span> db.session.commit()<br><span> 96</span> payload = {<br><span> 97</span> <span>"success"</span>: <span>True</span>,<br><span> 98</span> <span>"post"</span>: post.to_dict()<br><span> 99</span> }<br><span>100</span> <span>except</span> AttributeError:<br><span>101</span> payload = {<br><span>102</span> <span>"success"</span>: <span>False</span>,<br><span>103</span> <span>"errors"</span>: [<span>"item matching id {id} not found"</span>]<br><span>104</span> }<br><span>105</span> <span>return</span> payload<br><span>106</span><br><span>107</span><br><span>108</span><span>@convert_kwargs_to_snake_case</span><br><span>109</span><span><span>def</span> <span>delete_post_resolver</span><span>(obj, info, id)</span>:</span><br><span>110</span> <span>try</span>:<br><span>111</span> post = Post.query.get(id)<br><span>112</span> db.session.delete(post)<br><span>113</span> db.session.commit()<br><span>114</span> payload = {<span>"success"</span>: <span>True</span>, <span>"post"</span>: post.to_dict()}<br><span>115</span> <span>except</span> AttributeError:<br><span>116</span> payload = {<br><span>117</span> <span>"success"</span>: <span>False</span>,<br><span>118</span> <span>"errors"</span>: [<span>"Not found"</span>]<br><span>119</span> }<br><span>120</span> <span>return</span> payload<br><span>121</span><br><span>122</span><br><span>123</span><span># 定义 GraphQL 操作</span><br><span>124</span>query = ObjectType(<span>"Query"</span>)<br><span>125</span>mutation = ObjectType(<span>"Mutation"</span>)<br><span>126</span><br><span>127</span>query.set_field(<span>"listPosts"</span>, list_posts_resolver)<br><span>128</span>query.set_field(<span>"getPost"</span>, get_post_resolver)<br><span>129</span>mutation.set_field(<span>"createPost"</span>, create_post_resolver)<br><span>130</span>mutation.set_field(<span>"updatePost"</span>, update_post_resolver)<br><span>131</span>mutation.set_field(<span>"deletePost"</span>, delete_post_resolver)<br><span>132</span><br><span>133</span><span># 加载 Schema</span><br><span>134</span>type_defs = load_schema_from_path(<span>"./schema/schema.graphql"</span>)<br><span>135</span>schema = make_executable_schema(<br><span>136</span> type_defs, query, mutation, snake_case_fallback_resolvers<br><span>137</span>)<br><span>138</span><br><span>139</span><br><span>140</span><span>@app.route("/graphql", methods=["GET"])</span><br><span>141</span><span><span>def</span> <span>graphql_playground</span><span>()</span>:</span><br><span>142</span> <span>return</span> <span>"<h1>Hello, GraphQL</h1>"</span>, <span>200</span><br><span>143</span><br><span>144</span><br><span>145</span><span>@app.route("/graphql", methods=["POST"])</span><br><span>146</span><span><span>def</span> <span>graphql_server</span><span>()</span>:</span><br><span>147</span> data = request.get_json()<br><span>148</span> <span># 同步查询</span><br><span>149</span> success, result = graphql_sync(<br><span>150</span> schema,<br><span>151</span> data,<br><span>152</span> context_value=request,<br><span>153</span> debug=app.debug<br><span>154</span> )<br><span>155</span> status_code = <span>200</span> <span>if</span> success <span>else</span> <span>400</span><br><span>156</span> <span>return</span> jsonify(result), status_code<br><span>157</span><br><span>158</span><br><span>159</span><span>if</span> __name__ == <span>'__main__'</span>:<br><span>160</span> app.run(port=<span>4000</span>)<br>
测试客户端
Apollo Sandbox Explorer 提供了一个方便的 client 使用
创建 CreateNewPost 重试 错误原因
查询 ListPosts 重试 错误原因
GraphQL 优点
GraphQL 具有以下优点:
-
强大的查询能力:允许客户端精确地获取所需的数据,避免了过度获取和数据冗余。同时减少服务端变动。
-
清晰的 API 定义:通过 GraphQL 的模式定义,清晰地描述了服务器提供的数据结构和功能。
-
易于扩展:添加新的字段或类型不会影响现有客户端,因为客户端可以根据需要动态地获取数据。
-
高效的数据传输:只传输客户端所需的数据,减少了网络带宽的使用。例如,在一个电子商务应用中,客户端可以通过 GraphQL 查询特定产品的名称、价格、描述和库存数量,而不是获取整个产品表的数据。
-
强大的错误处理:明确的错误反馈有助于客户端快速解决问题。例如,如果某个字段不存在或无权访问,GraphQL 会返回明确的错误信息。
-
提高开发效率:使得客户端和服务器的开发更加高效和协作。
GraphQL 缺点
附加成本
理解和掌握 GraphQL 的概念和语法需要一定的学习成本。
并且构建高效的 GraphQL 架构需要仔细设计模式和解析器。
过度查询
GraphQL 为了灵活性而牺牲了一定的性能。
通常我们需要将数据全部查出来而不管是否需要,通过 GraphQL 来进行数据转换,过滤。
这就会导致过度查询。
好在我们可以自行解析请求 Schema 来获取所需字段,按字段来查询真实数据源。
<span> 1</span><span><span>def</span> <span>list_posts_resolver</span><span>(obj, info)</span>:</span><br><span> 2</span> <span># 拿到客户端所需要的字段</span><br><span> 3</span> fields = info.return_type.fields <span># 举例说明,未经过测试</span><br><span> 4</span> <span>try</span>:<br><span> 5</span> posts = [post.to_dict() <span>for</span> post <span>in</span> Post.query.where(fields=fields)]<br><span> 6</span> payload = {<br><span> 7</span> <span>"success"</span>: <span>True</span>,<br><span> 8</span> <span>"posts"</span>: posts<br><span> 9</span> }<br><span>10</span> <span>except</span> Exception <span>as</span> error:<br><span>11</span> payload = {<br><span>12</span> <span>"success"</span>: <span>False</span>,<br><span>13</span> <span>"errors"</span>: [str(error)]<br><span>14</span> }<br><span>15</span> <span>return</span> payload<br>
安全问题
GraphQL 的灵活性也来带了一定的安全问题。我们需要详细设计,避免将不能暴露给用户的字段泄漏。
参考
GraphQL官方文档
Apollo Sandbox Explorer 重试 错误原因
Apollo GraphQL tutorials 重试 错误原因
每日一句
写作就是思考,思考很难。
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240424/GraphQL-%E4%BB%8B%E7%BB%8D%E5%8F%8A-Python-%E5%AE%9E%E7%8E%B0--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com