shopify_api-graphql-tiny 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env.template +5 -0
- data/.github/workflows/ci.yml +2 -1
- data/Changes +4 -0
- data/README.md +129 -2
- data/lib/shopify_api/graphql/tiny/version.rb +3 -1
- data/lib/shopify_api/graphql/tiny.rb +197 -4
- data/shopify_api-graphql-tiny.gemspec +7 -1
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 125818846e86b77345ee59d8e6d03c4600b2aaa56f18e33ec1ae23ecfa90938e
|
4
|
+
data.tar.gz: 4b4443e43ff6d37fdbbad202222b6f5b3dff11f8f0a377260898dab06d2209ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5418a19e0a24b56d7e587c9a1bde22bf4d1c830639393fa30b371216dc6e8f9824cf1b31f477e6834ae17ec92c0594803a8b0593920a1b7db22655e5fc8439dd
|
7
|
+
data.tar.gz: b2ae36edf66dcb67395950e39231cbba3d13f0873b381a78b1cd8404f3e40fd3c7dbc157030c6665cc029ea6800611455cfb19da8fc5dfcd7fc08c3d45304631
|
data/.env.template
CHANGED
data/.github/workflows/ci.yml
CHANGED
@@ -11,10 +11,11 @@ jobs:
|
|
11
11
|
SHOPIFY_DOMAIN: "${{ secrets.SHOPIFY_DOMAIN }}"
|
12
12
|
SHOPIFY_TOKEN: "${{ secrets.SHOPIFY_TOKEN }}"
|
13
13
|
SHOPIFY_CUSTOMER_ID: "${{ secrets.SHOPIFY_CUSTOMER_ID }}"
|
14
|
+
SHOPIFY_PRODUCT_ID: "${{ secrets.SHOPIFY_PRODUCT_ID }}"
|
14
15
|
|
15
16
|
strategy:
|
16
17
|
matrix:
|
17
|
-
ruby: ["3.1", "3.0", "2.7.2", "2.6.6", "2.5.8", "2.4.10"]
|
18
|
+
ruby: ["3.2", "3.1", "3.0", "2.7.2", "2.6.6", "2.5.8", "2.4.10"]
|
18
19
|
|
19
20
|
steps:
|
20
21
|
- uses: actions/checkout@v2
|
data/Changes
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ShopifyAPI::GraphQL::Tiny
|
2
2
|
|
3
|
-
Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in retry.
|
3
|
+
Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in pagination and retry.
|
4
4
|
|
5
5
|
[![CI](https://github.com/ScreenStaring/shopify_api-graphql-tiny/actions/workflows/ci.yml/badge.svg)](https://github.com/ScreenStaring/shopify_api-graphql-tiny/actions)
|
6
6
|
|
@@ -10,6 +10,8 @@ Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in retry.
|
|
10
10
|
require "shopify_api/graphql/tiny"
|
11
11
|
|
12
12
|
gql = ShopifyAPI::GraphQL::Tiny.new("my-shop", token)
|
13
|
+
|
14
|
+
# Automatically retried
|
13
15
|
result = gql.execute(<<-GQL, :id => "gid://shopify/Customer/1283599123")
|
14
16
|
query findCustomer($id: ID!) {
|
15
17
|
customer(id: $id) {
|
@@ -33,6 +35,8 @@ p customer["tags"]
|
|
33
35
|
p customer.dig("metafields", "edges", 0, "node")["value"]
|
34
36
|
|
35
37
|
updates = { :id => customer["id"], :tags => customer["tags"] + %w[foo bar] }
|
38
|
+
|
39
|
+
# Automatically retried as well
|
36
40
|
result = gql.execute(<<-GQL, :input => updates)
|
37
41
|
mutation customerUpdate($input: CustomerInput!) {
|
38
42
|
customerUpdate(input: $input) {
|
@@ -50,7 +54,129 @@ GQL
|
|
50
54
|
p result.dig("data", "customerUpdate", "userErrors")
|
51
55
|
```
|
52
56
|
|
53
|
-
|
57
|
+
### Pagination
|
58
|
+
|
59
|
+
In addition to built-in request retry `ShopifyAPI::GraphQL::Tiny` also builds in support for pagination.
|
60
|
+
|
61
|
+
Using pagination requires you to include [the Shopify `PageInfo` object](https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo)
|
62
|
+
in your queries and wrap them in a function that accepts a page/cursor argument.
|
63
|
+
|
64
|
+
The pager's `#execute` is like the non-paginated `#execute` method and accepts additional, non-pagination query arguments:
|
65
|
+
|
66
|
+
```rb
|
67
|
+
pager.execute(query, :foo => 123)
|
68
|
+
```
|
69
|
+
|
70
|
+
And it accepts a block which will be passed each page returned by the query:
|
71
|
+
|
72
|
+
```rb
|
73
|
+
pager.execute(query, :foo => 123) do |page|
|
74
|
+
# do something with each page
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
#### `after` Pagination
|
79
|
+
|
80
|
+
To use `after` pagination, i.e., to paginate forward, your query must:
|
81
|
+
|
82
|
+
- Make the page/cursor argument optional
|
83
|
+
- Include `PageInfo`'s `hasNextPage` and `endCursor` fields
|
84
|
+
|
85
|
+
For example:
|
86
|
+
|
87
|
+
```rb
|
88
|
+
FIND_ORDERS = <<-GQL
|
89
|
+
query findOrders($after: String) {
|
90
|
+
orders(first: 10 after: $after) {
|
91
|
+
pageInfo {
|
92
|
+
hasNextPage
|
93
|
+
endCursor
|
94
|
+
}
|
95
|
+
edges {
|
96
|
+
node {
|
97
|
+
id
|
98
|
+
email
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
GQL
|
104
|
+
|
105
|
+
pager = gql.paginate # This is the same as gql.paginate(:after)
|
106
|
+
pager.execute(FIND_ORDERS) do |page|
|
107
|
+
orders = page.dig("data", "orders", "edges")
|
108
|
+
orders.each do |order|
|
109
|
+
# ...
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
By default it is assumed your GraphQL query uses a variable named `$after`. You can specify a different name using the `:variable`
|
115
|
+
option:
|
116
|
+
|
117
|
+
```rb
|
118
|
+
pager = gql.paginate(:after, :variable => "yourVariable")
|
119
|
+
```
|
120
|
+
|
121
|
+
#### `before` Pagination
|
122
|
+
|
123
|
+
To use `before` pagination, i.e. to paginate backward, your query must:
|
124
|
+
|
125
|
+
- Make the page/cursor argument **required**
|
126
|
+
- Include the `PageInfo`'s `hasPreviousPage` and `startCursor` fields
|
127
|
+
- Specify the `:before` argument to `#paginate`
|
128
|
+
|
129
|
+
For example:
|
130
|
+
|
131
|
+
```rb
|
132
|
+
FIND_ORDERS = <<-GQL
|
133
|
+
query findOrders($before: String) {
|
134
|
+
orders(last: 10 before: $before) {
|
135
|
+
pageInfo {
|
136
|
+
hasPreviousPage
|
137
|
+
startCursor
|
138
|
+
}
|
139
|
+
edges {
|
140
|
+
node {
|
141
|
+
id
|
142
|
+
email
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
GQL
|
148
|
+
|
149
|
+
pager = gql.paginate(:before)
|
150
|
+
pager.execute(FIND_ORDERS) do |page|
|
151
|
+
# ...
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
By default it is assumed your GraphQL query uses a variable named `$before`. You can specify a different name using the `:variable`
|
156
|
+
option:
|
157
|
+
|
158
|
+
```rb
|
159
|
+
pager = gql.paginate(:before, :variable => "yourVariable")
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Response Pagination Data
|
163
|
+
|
164
|
+
By default `ShopifyAPI::GraphQL::Tiny` will use the first `pageInfo` block with a next or previous page it finds
|
165
|
+
in the GraphQL response. If necessary you can specify an explicit location for the `pageInfo` block:
|
166
|
+
|
167
|
+
```rb
|
168
|
+
pager = gql.paginate(:after => %w[some path to it])
|
169
|
+
pager.execute(query) { |page| }
|
170
|
+
|
171
|
+
pager = gql.paginate(:after => ->(data) { data.dig("some", "path", "to", "it") })
|
172
|
+
pager.execute(query) { |page| }
|
173
|
+
```
|
174
|
+
|
175
|
+
The `"data"` and `"pageInfo"` keys are automatically added if not provided.
|
176
|
+
|
177
|
+
### Automatically Retrying Failed Requests
|
178
|
+
|
179
|
+
See [the docs](https://rubydoc.info/gems/shopify_api-graphql-tiny) for more information.
|
54
180
|
|
55
181
|
## Testing
|
56
182
|
|
@@ -59,6 +185,7 @@ See [the docs](https://rdoc.info/gems/shopify_api-graphql-tiny) for complete doc
|
|
59
185
|
## See Also
|
60
186
|
|
61
187
|
- [Shopify Dev Tools](https://github.com/ScreenStaring/shopify-dev-tools) - Command-line program to assist with the development and/or maintenance of Shopify apps and stores
|
188
|
+
- [Shopify ID Export](https://github.com/ScreenStaring/shopify_id_export/) Dump Shopify product and variant IDs —along with other identifiers— to a CSV or JSON file
|
62
189
|
- [ShopifyAPIRetry](https://github.com/ScreenStaring/shopify_api_retry) - Retry a ShopifyAPI request if rate-limited or other errors occur (REST and GraphQL APIs)
|
63
190
|
|
64
191
|
## License
|
@@ -43,6 +43,8 @@ module ShopifyAPI
|
|
43
43
|
QUERY_COST_HEADER = "X-GraphQL-Cost-Include-Fields"
|
44
44
|
|
45
45
|
DEFAULT_HEADERS = { "Content-Type" => "application/json" }.freeze
|
46
|
+
|
47
|
+
# Retry rules to be used for all instances if no rules are specified via the +:retry+ option when creating an instance
|
46
48
|
DEFAULT_RETRY_OPTIONS = {
|
47
49
|
ConnectionError => { :wait => 3, :tries => 20 },
|
48
50
|
GraphQLError => { :wait => 3, :tries => 20 },
|
@@ -96,21 +98,71 @@ module ShopifyAPI
|
|
96
98
|
#
|
97
99
|
# === Errors
|
98
100
|
#
|
99
|
-
# ConnectionError, HTTPError, RateLimitError, GraphQLError
|
101
|
+
# ArgumentError, ConnectionError, HTTPError, RateLimitError, GraphQLError
|
100
102
|
#
|
101
|
-
# * An HTTPError is raised of the response does not have 200 status code
|
102
|
-
# * A RateLimitError is raised if rate-limited and retries are disabled or if still
|
103
|
-
#
|
103
|
+
# * An ShopifyAPI::GraphQL::Tiny::HTTPError is raised of the response does not have 200 status code
|
104
|
+
# * A ShopifyAPI::GraphQL::Tiny::RateLimitError is raised if rate-limited and retries are disabled or if still
|
105
|
+
# rate-limited after the configured number of retry attempts
|
106
|
+
# * A ShopifyAPI::GraphQL::Tiny::GraphQLError is raised if the response contains an +errors+ property that is
|
107
|
+
# not a rate-limit error
|
104
108
|
#
|
105
109
|
# === Returns
|
106
110
|
#
|
107
111
|
# [Hash] The GraphQL response. Unmodified.
|
108
112
|
|
109
113
|
def execute(q, variables = nil)
|
114
|
+
raise ArgumentError, "query required" if q.nil? || q.to_s.strip.empty?
|
115
|
+
|
110
116
|
config = retry? ? @options[:retry] || DEFAULT_RETRY_OPTIONS : {}
|
111
117
|
ShopifyAPIRetry::GraphQL.retry(config) { post(q, variables) }
|
112
118
|
end
|
113
119
|
|
120
|
+
##
|
121
|
+
# Create a pager to execute a paginated query:
|
122
|
+
#
|
123
|
+
# pager = gql.paginate # This is the same as gql.paginate(:after)
|
124
|
+
# pager.execute(query, :id => id) do |page|
|
125
|
+
# page.dig("data", "product", "title")
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# The block is called for each page.
|
129
|
+
#
|
130
|
+
# Using pagination requires you to include the
|
131
|
+
# {PageInfo}[https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo]
|
132
|
+
# object in your queries and wrap them in a function that accepts a page/cursor argument.
|
133
|
+
# See the README for more information.
|
134
|
+
#
|
135
|
+
# === Arguments
|
136
|
+
#
|
137
|
+
# [direction (Symbol)] The direction to paginate, either +:after+ or +:before+. Optional, defaults to +:after:+
|
138
|
+
# [options (Hash)] Pagination options. Optional.
|
139
|
+
#
|
140
|
+
# === Options
|
141
|
+
#
|
142
|
+
# [:after (Array|Proc)] The location of {PageInfo}[https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo]
|
143
|
+
# block.
|
144
|
+
#
|
145
|
+
# An +Array+ will be passed directly to <code>Hash#dig</code>. A +TypeError+ resulting
|
146
|
+
# from the +#dig+ call will be raised as an +ArgumentError+.
|
147
|
+
#
|
148
|
+
# The <code>"data"</code> and <code>"pageInfo"</code> keys are automatically added if not provided.
|
149
|
+
#
|
150
|
+
# A +Proc+ must accept the GraphQL response +Hash+ as its argument and must return the
|
151
|
+
# +pageInfo+ block to use for pagination.
|
152
|
+
#
|
153
|
+
# [:before (Array|Proc)] See the +:after+ option
|
154
|
+
# [:variable (String)] Name of the GraphQL variable to use as the "page" argument.
|
155
|
+
# Defaults to <code>"before"</code> or <code>"after"</code>, depending on the pagination
|
156
|
+
# direction.
|
157
|
+
#
|
158
|
+
# === Errors
|
159
|
+
#
|
160
|
+
# ArgumentError
|
161
|
+
|
162
|
+
def paginate(*options)
|
163
|
+
Pager.new(self, options)
|
164
|
+
end
|
165
|
+
|
114
166
|
private
|
115
167
|
|
116
168
|
def retry?
|
@@ -166,6 +218,147 @@ module ShopifyAPI
|
|
166
218
|
raise GraphQLError.new(prefix + errors, json)
|
167
219
|
end
|
168
220
|
end
|
221
|
+
|
222
|
+
class Pager # :nodoc:
|
223
|
+
NEXT_PAGE_KEYS = {
|
224
|
+
:before => %w[hasPreviousPage startCursor].freeze,
|
225
|
+
:after => %w[hasNextPage endCursor].freeze
|
226
|
+
}.freeze
|
227
|
+
|
228
|
+
def initialize(gql, *options)
|
229
|
+
@gql = gql
|
230
|
+
@options = normalize_options(options)
|
231
|
+
end
|
232
|
+
|
233
|
+
def execute(q, variables = nil)
|
234
|
+
unless pagination_variable_exists?(q)
|
235
|
+
raise ArgumentError, "query does not contain the pagination variable '#{@options[:variable]}'"
|
236
|
+
end
|
237
|
+
|
238
|
+
variables ||= {}
|
239
|
+
pagination_finder = @options[@options[:direction]]
|
240
|
+
|
241
|
+
loop do
|
242
|
+
page = @gql.execute(q, variables)
|
243
|
+
|
244
|
+
yield page
|
245
|
+
|
246
|
+
cursor = pagination_finder[page]
|
247
|
+
break unless cursor
|
248
|
+
|
249
|
+
next_page_variables = variables.dup
|
250
|
+
next_page_variables[@options[:variable]] = cursor
|
251
|
+
#break unless next_page_variables != variables
|
252
|
+
|
253
|
+
variables = next_page_variables
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def normalize_options(options)
|
260
|
+
normalized = {}
|
261
|
+
|
262
|
+
options.flatten!
|
263
|
+
options.each do |option|
|
264
|
+
case option
|
265
|
+
when Hash
|
266
|
+
normalized.merge!(normalize_hash_option(option))
|
267
|
+
when *NEXT_PAGE_KEYS.keys
|
268
|
+
normalized[:direction] = option
|
269
|
+
else
|
270
|
+
raise ArgumentError, "invalid pagination option #{option}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
normalized[:direction] ||= :after
|
275
|
+
normalized[normalized[:direction]] ||= method(:default_pagination_finder)
|
276
|
+
|
277
|
+
normalized[:variable] ||= normalized[:direction].to_s
|
278
|
+
normalized[:variable] = normalized[:variable].sub(%r{\A\$}, "")
|
279
|
+
|
280
|
+
normalized
|
281
|
+
end
|
282
|
+
|
283
|
+
def normalize_hash_option(option)
|
284
|
+
normalized = option.dup
|
285
|
+
|
286
|
+
NEXT_PAGE_KEYS.each do |key, _|
|
287
|
+
next unless option.include?(key)
|
288
|
+
|
289
|
+
normalized[:direction] = key
|
290
|
+
|
291
|
+
case option[key]
|
292
|
+
when Proc
|
293
|
+
normalized[key] = ->(data) { extract_cursor(option[key][data]) }
|
294
|
+
when Array
|
295
|
+
path = pagination_path(option[key])
|
296
|
+
normalized[key] = ->(data) do
|
297
|
+
begin
|
298
|
+
extract_cursor(data.dig(*path))
|
299
|
+
rescue TypeError => e
|
300
|
+
# Use original path in error as not to confuse
|
301
|
+
raise ArgumentError, "invalid pagination path #{option[key]}: #{e}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
else
|
305
|
+
raise ArgumentError, "invalid pagination locator #{option[key]}"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
normalized
|
310
|
+
end
|
311
|
+
|
312
|
+
def pagination_path(user_path)
|
313
|
+
path = user_path.dup
|
314
|
+
|
315
|
+
# No need for this, we check for this key ourselves
|
316
|
+
path.pop if path[-1] == "pageInfo"
|
317
|
+
|
318
|
+
# Must always include this (sigh)
|
319
|
+
path.unshift("data") if path[0] != "data"
|
320
|
+
|
321
|
+
path
|
322
|
+
end
|
323
|
+
|
324
|
+
def pagination_variable_exists?(query)
|
325
|
+
name = Regexp.quote(@options[:variable])
|
326
|
+
query.match?(%r{\$#{name}\s*:})
|
327
|
+
end
|
328
|
+
|
329
|
+
def extract_cursor(data)
|
330
|
+
return unless data.is_a?(Hash)
|
331
|
+
|
332
|
+
has_next, next_cursor = NEXT_PAGE_KEYS[@options[:direction]]
|
333
|
+
|
334
|
+
pi = data["pageInfo"]
|
335
|
+
return unless pi && pi[has_next]
|
336
|
+
|
337
|
+
pi[next_cursor]
|
338
|
+
end
|
339
|
+
|
340
|
+
def default_pagination_finder(data)
|
341
|
+
cursor = nil
|
342
|
+
|
343
|
+
case data
|
344
|
+
when Hash
|
345
|
+
cursor = extract_cursor(data)
|
346
|
+
return cursor if cursor
|
347
|
+
|
348
|
+
data.values.each do |v|
|
349
|
+
cursor = default_pagination_finder(v)
|
350
|
+
break if cursor
|
351
|
+
end
|
352
|
+
when Array
|
353
|
+
data.each do |v|
|
354
|
+
cursor = default_pagination_finder(v)
|
355
|
+
break if cursor
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
cursor
|
360
|
+
end
|
361
|
+
end
|
169
362
|
end
|
170
363
|
|
171
364
|
GQL = GraphQL
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Skye Shaw"]
|
10
10
|
spec.email = ["skye.shaw@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = %q{Lightweight, no-nonsense, Shopify Admin API
|
12
|
+
spec.summary = %q{Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in pagination and retry}
|
13
13
|
spec.homepage = "https://github.com/ScreenStaring/shopify_api-graphql-tiny"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -21,6 +21,12 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.bindir = "exe"
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
|
+
spec.metadata = {
|
25
|
+
"bug_tracker_uri" => "https://github.com/ScreenStaring/shopify_api-graphql-tiny/issues",
|
26
|
+
"changelog_uri" => "https://github.com/ScreenStaring/shopify_api-graphql-tiny/blob/master/Changes",
|
27
|
+
"documentation_uri" => "https://rubydoc.info/gems/shopify_api-graphql-tiny",
|
28
|
+
"source_code_uri" => "https://github.com/ScreenStaring/shopify_api-graphql-tiny",
|
29
|
+
}
|
24
30
|
|
25
31
|
spec.add_dependency "shopify_api_retry", "~> 0.2"
|
26
32
|
spec.add_development_dependency "webmock", "~> 3.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify_api-graphql-tiny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: shopify_api_retry
|
@@ -104,7 +104,11 @@ files:
|
|
104
104
|
homepage: https://github.com/ScreenStaring/shopify_api-graphql-tiny
|
105
105
|
licenses:
|
106
106
|
- MIT
|
107
|
-
metadata:
|
107
|
+
metadata:
|
108
|
+
bug_tracker_uri: https://github.com/ScreenStaring/shopify_api-graphql-tiny/issues
|
109
|
+
changelog_uri: https://github.com/ScreenStaring/shopify_api-graphql-tiny/blob/master/Changes
|
110
|
+
documentation_uri: https://rubydoc.info/gems/shopify_api-graphql-tiny
|
111
|
+
source_code_uri: https://github.com/ScreenStaring/shopify_api-graphql-tiny
|
108
112
|
post_install_message:
|
109
113
|
rdoc_options: []
|
110
114
|
require_paths:
|
@@ -120,10 +124,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
124
|
- !ruby/object:Gem::Version
|
121
125
|
version: '0'
|
122
126
|
requirements: []
|
123
|
-
|
124
|
-
rubygems_version: 2.7.6
|
127
|
+
rubygems_version: 3.1.4
|
125
128
|
signing_key:
|
126
129
|
specification_version: 4
|
127
|
-
summary: Lightweight, no-nonsense, Shopify Admin API
|
128
|
-
retry
|
130
|
+
summary: Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in
|
131
|
+
pagination and retry
|
129
132
|
test_files: []
|