elasticgraph-indexer_lambda 0.18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0bfc417f9a3f74f5dea10bc89aa7b015c52a2423b9bae24f83c23072fcac94e0
4
+ data.tar.gz: 45845f3166226ff66f38dc0242be414d37d4db6d75254d55d6013097e41dcb2b
5
+ SHA512:
6
+ metadata.gz: 0fd527fd93ef7f9728a9f172c1544c6bd55f6f62d85319af211f07f5b0911496a1ea42df622a319d56b6690cc516ab76724acd03f8cc56963fbbdb839a24b179
7
+ data.tar.gz: c64da253570961c96c351d4eb0ed9e55d523d21ed5908a11ec0c6c59315381852def1c1a776afcab742141bac326b01c708156838f12f134dec59d63b6858d54
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Block, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # ElasticGraph::IndexerLambda
2
+
3
+ Adapts elasticgraph-indexer to run in an AWS Lambda.
4
+
5
+ ## SQS Message Payload Format
6
+
7
+ We use [JSON Lines](http://jsonlines.org/) to encode our indexing events. It is just individual JSON objects
8
+ delimited by a newline control character(not the `\n` string sequence), such as:
9
+
10
+ ```jsonl
11
+ {"op": "upsert", "__typename": "Payment", "id": "123", "version": "1", "record": {...} }
12
+ {"op": "upsert", "__typename": "Payment", "id": "123", "version": "2", record: {...} }
13
+ {"op": "delete", "__typename": "Payment", "id": "123", "version": "3"}
14
+ ```
15
+
16
+ However, due to SQS message size limit, we have to batch our events carefully so each batch is below the size limit.
17
+ This makes payload encoding a bit more complicated on the publisher side because each message has a size limit.
18
+ The following code snippet respects the max message size limit and sends JSON Lines payloads with proper size:
19
+
20
+ ```ruby
21
+ def partition_into_acceptably_sized_chunks(batch, max_size_per_chunk)
22
+ chunk_size = 0
23
+ batch
24
+ .map { |item| JSON.generate(item) }
25
+ .slice_before do |json|
26
+ chunk_size += (json.bytesize + 1)
27
+ (chunk_size > max_size_per_chunk).tap { |chunk_done| chunk_size = 0 if chunk_done }
28
+ end
29
+ .map { |chunk| chunk.join("\n") }
30
+ end
31
+ ```
@@ -0,0 +1,24 @@
1
+ # Copyright 2024 Block, Inc.
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ require_relative "../gemspec_helper"
10
+
11
+ ElasticGraphGemspecHelper.define_elasticgraph_gem(gemspec_file: __FILE__, category: :lambda) do |spec, eg_version|
12
+ spec.summary = "Provides an AWS Lambda interface for an elasticgraph API"
13
+
14
+ spec.add_dependency "elasticgraph-indexer", eg_version
15
+ spec.add_dependency "elasticgraph-lambda_support", eg_version
16
+ spec.add_dependency "aws-sdk-s3", "~> 1.146"
17
+
18
+ # aws-sdk-s3 requires an XML library be available. On Ruby < 3 it'll use rexml from the standard library but on Ruby 3.0+
19
+ # we have to add an explicit dependency. It supports ox, oga, libxml, nokogiri or rexml, and of those, ox seems to be the
20
+ # best choice: it leads benchmarks, is well-maintained, has no dependencies, and is MIT-licensed.
21
+ spec.add_dependency "ox", "~> 2.14"
22
+
23
+ spec.add_development_dependency "httpx", ">= 1.2.6", "< 2.0"
24
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright 2024 Block, Inc.
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ # :nocov: -- exercised by a test that loads this file in a sub-process, so not seen by SimpleCov.
10
+ require "elastic_graph/lambda_support/lambda_function"
11
+
12
+ module ElasticGraph
13
+ module IndexerLambda
14
+ class LambdaFunction
15
+ prepend LambdaSupport::LambdaFunction
16
+
17
+ def initialize
18
+ require "elastic_graph/indexer_lambda"
19
+ require "elastic_graph/indexer_lambda/sqs_processor"
20
+
21
+ indexer = ElasticGraph::IndexerLambda.indexer_from_env
22
+ @sqs_processor = ElasticGraph::IndexerLambda::SqsProcessor.new(
23
+ indexer.processor,
24
+ logger: indexer.logger,
25
+ report_batch_item_failures: ENV["REPORT_BATCH_ITEM_FAILURES"] == "true"
26
+ )
27
+ end
28
+
29
+ def handle_request(event:, context:)
30
+ @sqs_processor.process(event)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ # This is the constant used by the AWS Lambda function.
37
+ ProcessEventStreamEvent = ElasticGraph::IndexerLambda::LambdaFunction.new
38
+ # :nocov:
@@ -0,0 +1,142 @@
1
+ # Copyright 2024 Block, Inc.
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ require "elastic_graph/error"
10
+ require "elastic_graph/indexer/indexing_failures_error"
11
+ require "json"
12
+
13
+ module ElasticGraph
14
+ module IndexerLambda
15
+ # Responsible for handling lambda event payloads from an SQS lambda trigger.
16
+ class SqsProcessor
17
+ def initialize(indexer_processor, report_batch_item_failures:, logger:, s3_client: nil)
18
+ @indexer_processor = indexer_processor
19
+ @report_batch_item_failures = report_batch_item_failures
20
+ @logger = logger
21
+ @s3_client = s3_client
22
+ end
23
+
24
+ # Processes the ElasticGraph events in the given `lambda_event`, indexing the data in the database.
25
+ def process(lambda_event, refresh_indices: false)
26
+ events = events_from(lambda_event)
27
+ failures = @indexer_processor.process_returning_failures(events, refresh_indices: refresh_indices)
28
+
29
+ if failures.any?
30
+ failures_error = Indexer::IndexingFailuresError.for(failures: failures, events: events)
31
+ raise failures_error unless @report_batch_item_failures
32
+ @logger.error(failures_error.message)
33
+ end
34
+
35
+ format_response(failures)
36
+ end
37
+
38
+ private
39
+
40
+ # Given a lambda event payload, returns an array of raw operations in JSON format.
41
+ #
42
+ # The SQS payload is wrapped in the following format already:
43
+ # See https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#example-standard-queue-message-event for more details
44
+ # {
45
+ # Records: {
46
+ # [
47
+ # { body: <JSON Lines encoded JSON where each line is a JSON object> },
48
+ # { body: <JSON Lines encoded JSON where each line is a JSON object> },
49
+ # ...
50
+ # ]
51
+ # }
52
+ # }
53
+ #
54
+ # Each entry in "Records" is a SQS entry. Since lambda handles some batching
55
+ # for you (with some limits), you can get multiple.
56
+ #
57
+ # We also want to do our own batching in order to cram more into a given payload
58
+ # and issue fewer SQS entries and Lambda invocations when possible. As such, we
59
+ # encoded multiple JSON with JSON Lines (http://jsonlines.org/) in record body.
60
+ # Each JSON Lines object under 'body' should be of the form:
61
+ #
62
+ # {op: 'upsert', __typename: 'Payment', id: "123", version: "1", record: {...} } \n
63
+ # {op: 'upsert', __typename: 'Payment', id: "123", version: "2", record: {...} } \n
64
+ # ...
65
+ # Note: "\n" at the end of each line is a single byte newline control character, instead of a string sequence
66
+ def events_from(lambda_event)
67
+ lambda_event.fetch("Records").flat_map do |record|
68
+ sqs_metadata = extract_sqs_metadata(record)
69
+
70
+ parse_jsonl(record.fetch("body")).map do |event|
71
+ ElasticGraph::Support::HashUtil.deep_merge(event, sqs_metadata)
72
+ end
73
+ end
74
+ end
75
+
76
+ S3_OFFLOADING_INDICATOR = '["software.amazon.payloadoffloading.PayloadS3Pointer"'
77
+
78
+ def parse_jsonl(jsonl_string)
79
+ if jsonl_string.start_with?(S3_OFFLOADING_INDICATOR)
80
+ jsonl_string = get_payload_from_s3(jsonl_string)
81
+ end
82
+ jsonl_string.split("\n").map { |event| JSON.parse(event) }
83
+ end
84
+
85
+ def extract_sqs_metadata(record)
86
+ sqs_timestamps = {
87
+ "processing_first_attempted_at" => millis_to_iso8601(record.dig("attributes", "ApproximateFirstReceiveTimestamp")),
88
+ "sqs_received_at" => millis_to_iso8601(record.dig("attributes", "SentTimestamp"))
89
+ }.compact
90
+
91
+ {
92
+ "latency_timestamps" => (sqs_timestamps unless sqs_timestamps.empty?),
93
+ "message_id" => record["messageId"]
94
+ }.compact
95
+ end
96
+
97
+ def millis_to_iso8601(millis)
98
+ return unless millis
99
+ seconds, millis = millis.to_i.divmod(1000)
100
+ Time.at(seconds, millis, :millisecond).getutc.iso8601(3)
101
+ end
102
+
103
+ def get_payload_from_s3(json_string)
104
+ s3_pointer = JSON.parse(json_string)[1]
105
+ bucket_name = s3_pointer.fetch("s3BucketName")
106
+ object_key = s3_pointer.fetch("s3Key")
107
+
108
+ begin
109
+ s3_client.get_object(bucket: bucket_name, key: object_key).body.read
110
+ rescue Aws::S3::Errors::ServiceError => e
111
+ raise S3OperationFailedError, "Error reading large message from S3. bucket: `#{bucket_name}` key: `#{object_key}` message: `#{e.message}`"
112
+ end
113
+ end
114
+
115
+ # The s3 client is being lazily initialized, as it's slow to import/init and it will only be used
116
+ # in rare scenarios where large messages need offloaded from SQS -> S3.
117
+ # See: (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-s3-messages.html)
118
+ def s3_client
119
+ @s3_client ||= begin
120
+ require "aws-sdk-s3"
121
+ Aws::S3::Client.new
122
+ end
123
+ end
124
+
125
+ # Formats the response, including any failures, based on
126
+ # https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting
127
+ def format_response(failures)
128
+ failure_ids = failures.map do |failure| # $ {"itemIdentifier" => String}
129
+ {"itemIdentifier" => failure.event["message_id"]}
130
+ end
131
+
132
+ if failure_ids.any? { |f| f.fetch("itemIdentifier").nil? }
133
+ # If we are not able to identify one or more failed events, then we must raise an exception instead of
134
+ # returning `batchItemFailures`. Otherwise, the unidentified failed events will not get retried.
135
+ raise MessageIdsMissingError, "Unexpected: some failures did not have a `message_id`, so we are raising an exception instead of returning `batchItemFailures`."
136
+ end
137
+
138
+ {"batchItemFailures" => failure_ids}
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright 2024 Block, Inc.
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ require "elastic_graph/indexer"
10
+ require "elastic_graph/lambda_support"
11
+
12
+ module ElasticGraph
13
+ module IndexerLambda
14
+ # Builds an `ElasticGraph::Indexer` instance from our lambda ENV vars.
15
+ def self.indexer_from_env
16
+ LambdaSupport.build_from_env(Indexer)
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,306 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elasticgraph-indexer_lambda
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.18.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Myron Marston
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop-factory_bot
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.26'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.26'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop-rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: standard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.39.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.39.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: steep
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: coderay
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: flatware-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 2.3.2
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '3.0'
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 2.3.2
114
+ - - "<"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.13'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.13'
131
+ - !ruby/object:Gem::Dependency
132
+ name: super_diff
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 0.12.1
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 0.12.1
145
+ - !ruby/object:Gem::Dependency
146
+ name: simplecov
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '0.22'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.22'
159
+ - !ruby/object:Gem::Dependency
160
+ name: simplecov-console
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: 0.9.1
166
+ - - "<"
167
+ - !ruby/object:Gem::Version
168
+ version: '1.0'
169
+ type: :development
170
+ prerelease: false
171
+ version_requirements: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: 0.9.1
176
+ - - "<"
177
+ - !ruby/object:Gem::Version
178
+ version: '1.0'
179
+ - !ruby/object:Gem::Dependency
180
+ name: aws_lambda_ric
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: 2.0.0
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: 2.0.0
193
+ - !ruby/object:Gem::Dependency
194
+ name: elasticgraph-indexer
195
+ requirement: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - '='
198
+ - !ruby/object:Gem::Version
199
+ version: 0.18.0.0
200
+ type: :runtime
201
+ prerelease: false
202
+ version_requirements: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - '='
205
+ - !ruby/object:Gem::Version
206
+ version: 0.18.0.0
207
+ - !ruby/object:Gem::Dependency
208
+ name: elasticgraph-lambda_support
209
+ requirement: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - '='
212
+ - !ruby/object:Gem::Version
213
+ version: 0.18.0.0
214
+ type: :runtime
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - '='
219
+ - !ruby/object:Gem::Version
220
+ version: 0.18.0.0
221
+ - !ruby/object:Gem::Dependency
222
+ name: aws-sdk-s3
223
+ requirement: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - "~>"
226
+ - !ruby/object:Gem::Version
227
+ version: '1.146'
228
+ type: :runtime
229
+ prerelease: false
230
+ version_requirements: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - "~>"
233
+ - !ruby/object:Gem::Version
234
+ version: '1.146'
235
+ - !ruby/object:Gem::Dependency
236
+ name: ox
237
+ requirement: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - "~>"
240
+ - !ruby/object:Gem::Version
241
+ version: '2.14'
242
+ type: :runtime
243
+ prerelease: false
244
+ version_requirements: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - "~>"
247
+ - !ruby/object:Gem::Version
248
+ version: '2.14'
249
+ - !ruby/object:Gem::Dependency
250
+ name: httpx
251
+ requirement: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - ">="
254
+ - !ruby/object:Gem::Version
255
+ version: 1.2.6
256
+ - - "<"
257
+ - !ruby/object:Gem::Version
258
+ version: '2.0'
259
+ type: :development
260
+ prerelease: false
261
+ version_requirements: !ruby/object:Gem::Requirement
262
+ requirements:
263
+ - - ">="
264
+ - !ruby/object:Gem::Version
265
+ version: 1.2.6
266
+ - - "<"
267
+ - !ruby/object:Gem::Version
268
+ version: '2.0'
269
+ description:
270
+ email:
271
+ - myron@squareup.com
272
+ executables: []
273
+ extensions: []
274
+ extra_rdoc_files: []
275
+ files:
276
+ - LICENSE.txt
277
+ - README.md
278
+ - elasticgraph-indexer_lambda.gemspec
279
+ - lib/elastic_graph/indexer_lambda.rb
280
+ - lib/elastic_graph/indexer_lambda/lambda_function.rb
281
+ - lib/elastic_graph/indexer_lambda/sqs_processor.rb
282
+ homepage:
283
+ licenses:
284
+ - MIT
285
+ metadata:
286
+ gem_category: lambda
287
+ post_install_message:
288
+ rdoc_options: []
289
+ require_paths:
290
+ - lib
291
+ required_ruby_version: !ruby/object:Gem::Requirement
292
+ requirements:
293
+ - - "~>"
294
+ - !ruby/object:Gem::Version
295
+ version: '3.2'
296
+ required_rubygems_version: !ruby/object:Gem::Requirement
297
+ requirements:
298
+ - - ">="
299
+ - !ruby/object:Gem::Version
300
+ version: '0'
301
+ requirements: []
302
+ rubygems_version: 3.5.9
303
+ signing_key:
304
+ specification_version: 4
305
+ summary: Provides an AWS Lambda interface for an elasticgraph API
306
+ test_files: []