functions_framework 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6d562086a839aaf78661b9417d3b931df10c74c98cb07b1710d40d6294b0490
4
- data.tar.gz: fd070c121ce3e0c1bab7f1a738178a37c72c5d25c7726d3f3540e687d01bbe25
3
+ metadata.gz: d313c253cf23573fe55628885064699cf1da8ca51545446cd3a6adf58ffaf3ff
4
+ data.tar.gz: 2b82761a46c066f2a32af713c85e559afe5fe892bc1c761ec765772dd2633db2
5
5
  SHA512:
6
- metadata.gz: 478554bfc8b8bafb2efd5bb880b8e053e2442a878bcd1363a4c6ddeab77a811b49530b84f467009b01f2299b70105f17a8a9840d9b35ebbec94359ca14d7114c
7
- data.tar.gz: 6873da52a257f542cb782fde59ba7da7f96042f4b69eaf470ea23d81cdb8479e8ff2791a6fbd0fd36ec166039f9718b7ce1b14632c690b44bea2833e280835b5
6
+ metadata.gz: b58f6f2c3fbebf5cf6c5eb28031a4ec7a389c063b9f2b8edba6b7e346f20683a8a9339202e8075299c67c5a347313c37bd82aac47422981008858e301133cccf
7
+ data.tar.gz: fbd5515ed316ab193eec32efe17eb66cc937582d1af9b6196854bac8d2dee16de6d0b7429aac66ce287b97043b5dfadc35d989451e7bb3495d62dee512cd1681
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.8.0 / 2021-03-02
4
+
5
+ * ADDED: Support for lazily-initialized globals
6
+
3
7
  ### v0.7.1 / 2021-01-26
4
8
 
5
9
  * DOCS: Fixed several errors in the writing-functions doc samples
data/README.md CHANGED
@@ -60,7 +60,7 @@ Create a `Gemfile` listing the Functions Framework as a dependency:
60
60
  ```ruby
61
61
  # Gemfile
62
62
  source "https://rubygems.org"
63
- gem "functions_framework", "~> 0.7"
63
+ gem "functions_framework", "~> 0.8"
64
64
  ```
65
65
 
66
66
  Create a file called `app.rb` and include the following code. This defines a
data/docs/overview.md CHANGED
@@ -64,7 +64,7 @@ Create a `Gemfile` listing the Functions Framework as a dependency:
64
64
  ```ruby
65
65
  # Gemfile
66
66
  source "https://rubygems.org"
67
- gem "functions_framework", "~> 0.7"
67
+ gem "functions_framework", "~> 0.8"
68
68
  ```
69
69
 
70
70
  Create a file called `app.rb` and include the following code. This defines a
@@ -111,7 +111,7 @@ dependency on Sinatra in your `Gemfile`:
111
111
 
112
112
  ```ruby
113
113
  source "https://rubygems.org"
114
- gem "functions_framework", "~> 0.7"
114
+ gem "functions_framework", "~> 0.8"
115
115
  gem "sinatra", "~> 2.0"
116
116
  ```
117
117
 
@@ -248,10 +248,10 @@ FunctionsFramework.http "hello" do |request|
248
248
  end
249
249
  ```
250
250
 
251
- Startup tasks are run once per Ruby instance, before the framework starts
252
- receiving requests and executing functions. You can define multiple startup
253
- tasks, and they will run in order, and are guaranteed to complete before any
254
- function is executed.
251
+ Startup tasks are run once per Ruby instance during cold start -- that is,
252
+ after the Ruby VM boots up but before the framework starts receiving requests
253
+ and executing functions. You can define multiple startup tasks, and they will
254
+ run in order, and are guaranteed to complete before any function is executed.
255
255
 
256
256
  The block is optionally passed the {FunctionsFramework::Function} representing
257
257
  the function that will be run. You code can, for example, perform different
@@ -286,6 +286,11 @@ end
286
286
  # ...
287
287
  ```
288
288
 
289
+ Because startup tasks run during cold start, they could have an impact on your
290
+ function's startup latency. To mitigate this issue, it is possible to run parts
291
+ of your initialization lazily, as described below in the section below on
292
+ [lazy initialization](#Lazy_initialization).
293
+
289
294
  ### The execution context and global data
290
295
 
291
296
  When your function block executes, the _object context_ (i.e. `self`) is set to
@@ -331,8 +336,9 @@ resources, as described below.
331
336
  Using the global data mechanism is generally preferred over actual Ruby global
332
337
  variables, because the Functions Framework can help you avoid concurrent edits.
333
338
  Additionally, the framework will isolate the sets of global data associated
334
- with different sets of functions, which lets you test functions in isolation
335
- without the tests interfering with one another by writing to global variables.
339
+ with different sets of functions, which lets you run functions in isolation
340
+ during unit tests. If you are testing multiple functions, they will not
341
+ interfere with each other as they might if they used global variables.
336
342
 
337
343
  ### Sharing resources
338
344
 
@@ -345,10 +351,10 @@ re-establishing it for every function invocation.
345
351
 
346
352
  The best practice for sharing a resource across function invocations is to
347
353
  initialize it in a {FunctionsFramework.on_startup} block, and reference it from
348
- global shared data. (As discussed above, prefer to initialize shared resources
349
- in a startup task rather than at the top level of a Ruby file, and prefer using
350
- the Functions Framework's global data mechanism rather than Ruby's global
351
- variables.)
354
+ global shared data. (As discussed above, the best practice is to initialize
355
+ shared resources in a startup task rather than at the top level of a Ruby file,
356
+ and to use the Functions Framework's global data mechanism rather than Ruby's
357
+ global variables.)
352
358
 
353
359
  Here is a simple example:
354
360
 
@@ -383,6 +389,48 @@ may perform CPU throttling, and therefore there may not be an opportunity for
383
389
  cleanup tasks to run. (For example, you could register a `Kernel.at_exit` task,
384
390
  but the Ruby VM may still terminate without calling it.)
385
391
 
392
+ ### Lazy initialization
393
+
394
+ Because startup tasks run during cold start, they could have an impact on your
395
+ function's startup latency. You can mitigate this by initializing some globals
396
+ _lazily_. When setting a global, instead of computing and setting the value
397
+ directly (e.g. constructing a shared API client object directly), you can
398
+ provide a block that describes how to construct it on demand.
399
+
400
+ Here is an example using the storage client we saw above.
401
+
402
+ ```ruby
403
+ require "functions_framework"
404
+
405
+ # This startup block describes _how_ to initialize a shared client, but
406
+ # does not construct it immediately.
407
+ FunctionsFramework.on_startup do
408
+ require "google/cloud/storage"
409
+ set_global :storage_client do
410
+ Google::Cloud::Storage.new
411
+ end
412
+ end
413
+
414
+ # The first time this function is invoked, it will call the above block
415
+ # to construct the storage client. Subsequent invocations will not need
416
+ # to construct it again, but will reuse the same shared object.
417
+ FunctionsFramework.http "storage_example" do |request|
418
+ bucket = global(:storage_client).bucket "my-bucket"
419
+ file = bucket.file "path/to/my-file.txt"
420
+ file.download.to_s
421
+ end
422
+ ```
423
+
424
+ The block will not be called until a function actually attempts to access the
425
+ global. From that point, subsequent accesses of the global will return that
426
+ same shared value; the block will be called at most once. This is true even if
427
+ multiple functions are run concurrently in different threads.
428
+
429
+ Lazy initialization is particularly useful if you define several different
430
+ functions that may use different sets of shared resources. Instead of
431
+ initializing all resources eagerly up front, you could initialize them lazily
432
+ and run only the code needed by the function that is actually invoked.
433
+
386
434
  ## Structuring a project
387
435
 
388
436
  A Functions Framework based "project" or "application" is a typical Ruby
@@ -422,7 +470,7 @@ Following is a typical layout for a Functions Framework based project.
422
470
  ```ruby
423
471
  # Gemfile
424
472
  source "https://rubygems.org"
425
- gem "functions_framework", "~> 0.7"
473
+ gem "functions_framework", "~> 0.8"
426
474
  ```
427
475
 
428
476
  ```ruby
@@ -74,7 +74,7 @@ module FunctionsFramework
74
74
  # @param argv [Array<String>]
75
75
  # @return [self]
76
76
  #
77
- def parse_args argv # rubocop:disable Metrics/MethodLength
77
+ def parse_args argv # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
78
78
  @option_parser = ::OptionParser.new do |op| # rubocop:disable Metrics/BlockLength
79
79
  op.on "-t", "--target TARGET",
80
80
  "Set the name of the function to execute (defaults to #{DEFAULT_TARGET})" do |val|
@@ -164,6 +164,28 @@ module FunctionsFramework
164
164
  callable.call(*args)
165
165
  end
166
166
 
167
+ ##
168
+ # A lazy evaluator for a global
169
+ # @private
170
+ #
171
+ class LazyGlobal
172
+ def initialize block
173
+ @block = block
174
+ @value = nil
175
+ @mutex = ::Mutex.new
176
+ end
177
+
178
+ def value
179
+ @mutex.synchronize do
180
+ if @block
181
+ @value = @block.call
182
+ @block = nil
183
+ end
184
+ @value
185
+ end
186
+ end
187
+ end
188
+
167
189
  ##
168
190
  # A base class for a callable object that provides calling context.
169
191
  #
@@ -196,7 +218,9 @@ module FunctionsFramework
196
218
  # @return [Object]
197
219
  #
198
220
  def global key
199
- @__globals[key]
221
+ value = @__globals[key]
222
+ value = value.value if value.is_a? LazyGlobal
223
+ value
200
224
  end
201
225
 
202
226
  ##
@@ -204,11 +228,35 @@ module FunctionsFramework
204
228
  # are frozen when the server starts, so this call will raise an exception
205
229
  # if called from a normal function.
206
230
  #
207
- # @param key [Symbol,String]
208
- # @param value [Object]
231
+ # You can set a global to a final value, or you can provide a block that
232
+ # lazily computes the global the first time it is requested.
233
+ #
234
+ # @overload set_global(key, value)
235
+ # Set the given global to the given value. For example:
236
+ #
237
+ # set_global(:project_id, "my-project-id")
238
+ #
239
+ # @param key [Symbol,String]
240
+ # @param value [Object]
241
+ # @return [self]
242
+ #
243
+ # @overload set_global(key, &block)
244
+ # Call the given block to compute the global's value only when the
245
+ # value is actually requested. This block will be called at most once,
246
+ # and its result reused for subsequent calls. For example:
247
+ #
248
+ # set_global(:connection_pool) do
249
+ # ExpensiveConnectionPool.new
250
+ # end
251
+ #
252
+ # @param key [Symbol,String]
253
+ # @param block [Proc] A block that lazily computes a value
254
+ # @yieldreturn [Object] The value
255
+ # @return [self]
209
256
  #
210
- def set_global key, value
211
- @__globals[key] = value
257
+ def set_global key, value = nil, &block
258
+ @__globals[key] = block ? LazyGlobal.new(block) : value
259
+ self
212
260
  end
213
261
 
214
262
  ##
@@ -49,16 +49,19 @@ module FunctionsFramework
49
49
  end
50
50
 
51
51
  def normalized_context input
52
- raw_context = input["context"]
53
- id = raw_context&.[]("eventId") || input["eventId"]
54
- timestamp = raw_context&.[]("timestamp") || input["timestamp"]
55
- type = raw_context&.[]("eventType") || input["eventType"]
56
- service, resource = analyze_resource raw_context&.[]("resource") || input["resource"]
52
+ id = normalized_context_field input, "eventId"
53
+ timestamp = normalized_context_field input, "timestamp"
54
+ type = normalized_context_field input, "eventType"
55
+ service, resource = analyze_resource normalized_context_field input, "resource"
57
56
  service ||= service_from_type type
58
57
  return nil unless id && timestamp && type && service && resource
59
58
  { id: id, timestamp: timestamp, type: type, service: service, resource: resource }
60
59
  end
61
60
 
61
+ def normalized_context_field input, field
62
+ input["context"]&.[](field) || input[field]
63
+ end
64
+
62
65
  def analyze_resource raw_resource
63
66
  service = resource = nil
64
67
  case raw_resource
@@ -158,7 +158,7 @@ module FunctionsFramework
158
158
  ::Signal.trap "SIGHUP" do
159
159
  Server.signal_enqueue "SIGHUP", @config.logger, @server
160
160
  end
161
- rescue ::ArgumentError # rubocop:disable Lint/HandleExceptions
161
+ rescue ::ArgumentError
162
162
  # Not available on all systems
163
163
  end
164
164
  @signals_installed = true
@@ -366,9 +366,10 @@ module FunctionsFramework
366
366
  ::Rack::RACK_ERRORS => ::StringIO.new
367
367
  }
368
368
  headers.each do |header|
369
- if header.is_a? String
369
+ case header
370
+ when String
370
371
  name, value = header.split ":"
371
- elsif header.is_a? Array
372
+ when ::Array
372
373
  name, value = header
373
374
  end
374
375
  next unless name && value
@@ -17,5 +17,5 @@ module FunctionsFramework
17
17
  # Version of the Ruby Functions Framework
18
18
  # @return [String]
19
19
  #
20
- VERSION = "0.7.1".freeze
20
+ VERSION = "0.8.0".freeze
21
21
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: functions_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-26 00:00:00.000000000 Z
11
+ date: 2021-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cloud_events
@@ -87,10 +87,10 @@ homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
87
87
  licenses:
88
88
  - Apache-2.0
89
89
  metadata:
90
- changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.7.1/file.CHANGELOG.html
90
+ changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.8.0/file.CHANGELOG.html
91
91
  source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
92
92
  bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
93
- documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.7.1
93
+ documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.8.0
94
94
  post_install_message:
95
95
  rdoc_options: []
96
96
  require_paths:
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  - !ruby/object:Gem::Version
107
107
  version: '0'
108
108
  requirements: []
109
- rubygems_version: 3.1.4
109
+ rubygems_version: 3.2.11
110
110
  signing_key:
111
111
  specification_version: 4
112
112
  summary: Functions Framework for Ruby