functions_framework 0.5.0 → 0.7.1

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.
@@ -12,21 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require "monitor"
16
-
17
15
  module FunctionsFramework
18
16
  ##
19
17
  # Registry providing lookup of functions by name.
20
18
  #
21
19
  class Registry
22
- include ::MonitorMixin
23
-
24
20
  ##
25
21
  # Create a new empty registry.
26
22
  #
27
23
  def initialize
28
- super()
24
+ @mutex = ::Mutex.new
29
25
  @functions = {}
26
+ @start_tasks = []
30
27
  end
31
28
 
32
29
  ##
@@ -37,7 +34,7 @@ module FunctionsFramework
37
34
  # @return [nil] if the function is not found
38
35
  #
39
36
  def [] name
40
- @functions[name.to_s]
37
+ @mutex.synchronize { @functions[name.to_s] }
41
38
  end
42
39
 
43
40
  ##
@@ -46,7 +43,16 @@ module FunctionsFramework
46
43
  # @return [Array<String>]
47
44
  #
48
45
  def names
49
- @functions.keys.sort
46
+ @mutex.synchronize { @functions.keys.sort }
47
+ end
48
+
49
+ ##
50
+ # Return an array of startup tasks.
51
+ #
52
+ # @return [Array<FunctionsFramework::Function>]
53
+ #
54
+ def startup_tasks
55
+ @mutex.synchronize { @start_tasks.dup }
50
56
  end
51
57
 
52
58
  ##
@@ -68,9 +74,9 @@ module FunctionsFramework
68
74
  #
69
75
  def add_http name, &block
70
76
  name = name.to_s
71
- synchronize do
77
+ @mutex.synchronize do
72
78
  raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
73
- @functions[name] = Function.new name, :http, &block
79
+ @functions[name] = Function.http name, &block
74
80
  end
75
81
  self
76
82
  end
@@ -80,7 +86,7 @@ module FunctionsFramework
80
86
  #
81
87
  # You must provide a name for the function, and a block that implemets the
82
88
  # function. The block should take _one_ argument: the event object of type
83
- # [`CloudEvents::Event`](https://rubydoc.info/gems/cloud_events/CloudEvents/Event).
89
+ # [`CloudEvents::Event`](https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event).
84
90
  # Any return value is ignored.
85
91
  #
86
92
  # @param name [String] The function name
@@ -89,9 +95,26 @@ module FunctionsFramework
89
95
  #
90
96
  def add_cloud_event name, &block
91
97
  name = name.to_s
92
- synchronize do
98
+ @mutex.synchronize do
93
99
  raise ::ArgumentError, "Function already defined: #{name}" if @functions.key? name
94
- @functions[name] = Function.new name, :cloud_event, &block
100
+ @functions[name] = Function.cloud_event name, &block
101
+ end
102
+ self
103
+ end
104
+
105
+ ##
106
+ # Add a startup task.
107
+ #
108
+ # Startup tasks are generally run just before a server starts. They are
109
+ # passed the {FunctionsFramework::Function} identifying the function to
110
+ # execute, and have no return value.
111
+ #
112
+ # @param block [Proc] The startup task
113
+ # @return [self]
114
+ #
115
+ def add_startup_task &block
116
+ @mutex.synchronize do
117
+ @start_tasks << Function.startup_task(&block)
95
118
  end
96
119
  self
97
120
  end
@@ -27,17 +27,22 @@ module FunctionsFramework
27
27
  include ::MonitorMixin
28
28
 
29
29
  ##
30
- # Create a new web server given a function. Yields a
31
- # {FunctionsFramework::Server::Config} object that you can use to set
32
- # server configuration parameters. This block is the only opportunity to
33
- # set configuration; once the server is initialized, configuration is
34
- # frozen.
30
+ # Create a new web server given a function definition, a set of application
31
+ # globals, and server configuration.
32
+ #
33
+ # To configure the server, pass a block that takes a
34
+ # {FunctionsFramework::Server::Config} object as the parameter. This block
35
+ # is the only opportunity to modify the configuration; once the server is
36
+ # initialized, configuration is frozen.
35
37
  #
36
38
  # @param function [FunctionsFramework::Function] The function to execute.
39
+ # @param globals [Hash] Globals to pass to invocations. This hash should
40
+ # normally be frozen so separate function invocations cannot interfere
41
+ # with one another's globals.
37
42
  # @yield [FunctionsFramework::Server::Config] A config object that can be
38
43
  # manipulated to configure this server.
39
44
  #
40
- def initialize function
45
+ def initialize function, globals
41
46
  super()
42
47
  @config = Config.new
43
48
  yield @config if block_given?
@@ -46,9 +51,9 @@ module FunctionsFramework
46
51
  @app =
47
52
  case function.type
48
53
  when :http
49
- HttpApp.new function, @config
54
+ HttpApp.new function, globals, @config
50
55
  when :cloud_event
51
- EventApp.new function, @config
56
+ EventApp.new function, globals, @config
52
57
  else
53
58
  raise "Unrecognized function type: #{function.type}"
54
59
  end
@@ -82,9 +87,9 @@ module FunctionsFramework
82
87
  @server.max_threads = @config.max_threads
83
88
  @server.leak_stack_on_error = @config.show_error_details?
84
89
  @server.binder.add_tcp_listener @config.bind_addr, @config.port
85
- @server.run true
86
90
  @config.logger.info "FunctionsFramework: Serving function #{@function.name.inspect}" \
87
91
  " on port #{@config.port}..."
92
+ @server.run true
88
93
  end
89
94
  end
90
95
  self
@@ -379,9 +384,10 @@ module FunctionsFramework
379
384
 
380
385
  ## @private
381
386
  class HttpApp < AppBase
382
- def initialize function, config
387
+ def initialize function, globals, config
383
388
  super config
384
389
  @function = function
390
+ @globals = globals
385
391
  end
386
392
 
387
393
  def call env
@@ -391,8 +397,7 @@ module FunctionsFramework
391
397
  logger = env["rack.logger"] ||= @config.logger
392
398
  request = ::Rack::Request.new env
393
399
  logger.info "FunctionsFramework: Handling HTTP #{request.request_method} request"
394
- calling_context = @function.new_call logger: logger
395
- calling_context.call request
400
+ @function.call request, globals: @globals, logger: logger
396
401
  rescue ::StandardError => e
397
402
  e
398
403
  end
@@ -402,9 +407,10 @@ module FunctionsFramework
402
407
 
403
408
  ## @private
404
409
  class EventApp < AppBase
405
- def initialize function, config
410
+ def initialize function, globals, config
406
411
  super config
407
412
  @function = function
413
+ @globals = globals
408
414
  @cloud_events = ::CloudEvents::HttpBinding.default
409
415
  @legacy_events = LegacyEventConverter.new
410
416
  end
@@ -439,8 +445,7 @@ module FunctionsFramework
439
445
 
440
446
  def handle_cloud_event event, logger
441
447
  logger.info "FunctionsFramework: Handling CloudEvent"
442
- calling_context = @function.new_call logger: logger
443
- calling_context.call event
448
+ @function.call event, globals: @globals, logger: logger
444
449
  "ok"
445
450
  rescue ::StandardError => e
446
451
  e
@@ -75,19 +75,71 @@ module FunctionsFramework
75
75
  Testing.load_for_testing path, &block
76
76
  end
77
77
 
78
+ ##
79
+ # Run startup tasks for the given function name and return the initialized
80
+ # globals hash.
81
+ #
82
+ # Normally, this will be run automatically prior to the first call to the
83
+ # function using {call_http} or {call_event}, if it has not already been
84
+ # run. However, you can call it explicitly to test its behavior. It cannot
85
+ # be called more than once for any given function.
86
+ #
87
+ # By default, the {FunctionsFramework.logger} will be used, but you can
88
+ # override that by providing your own logger. In particular, to disable
89
+ # logging, you can pass `Logger.new(nil)`.
90
+ #
91
+ # @param name [String] The name of the function to start up.
92
+ # @param logger [Logger] Use the given logger instead of the Functions
93
+ # Framework's global logger. Optional.
94
+ # @param lenient [Boolean] If false (the default), raise an error if the
95
+ # given function has already had its startup tasks run. If true,
96
+ # duplicate requests to run startup tasks are ignored.
97
+ # @return [Hash] The initialized globals.
98
+ #
99
+ def run_startup_tasks name, logger: nil, lenient: false
100
+ function = Testing.current_registry[name]
101
+ raise "Unknown function name #{name}" unless function
102
+ globals = Testing.current_globals name
103
+ if globals
104
+ raise "Function #{name} has already been started up" unless lenient
105
+ else
106
+ globals = function.populate_globals
107
+ Testing.current_registry.startup_tasks.each do |task|
108
+ task.call function, globals: globals, logger: logger
109
+ end
110
+ Testing.current_globals name, globals
111
+ end
112
+ globals.freeze
113
+ end
114
+
78
115
  ##
79
116
  # Call the given HTTP function for testing. The underlying function must
80
- # be of type `:http`.
117
+ # be of type `:http`. Returns the Rack response.
118
+ #
119
+ # By default, the startup tasks will be run for the given function if they
120
+ # have not already been run. You can, however, disable running startup
121
+ # tasks by providing an explicit globals hash.
122
+ #
123
+ # By default, the {FunctionsFramework.logger} will be used, but you can
124
+ # override that by providing your own logger. In particular, to disable
125
+ # logging, you can pass `Logger.new(nil)`.
81
126
  #
82
127
  # @param name [String] The name of the function to call
83
128
  # @param request [Rack::Request] The Rack request to send
129
+ # @param globals [Hash] Do not run startup tasks, and instead provide the
130
+ # globals directly. Optional.
131
+ # @param logger [Logger] Use the given logger instead of the Functions
132
+ # Framework's global logger. Optional.
84
133
  # @return [Rack::Response]
85
134
  #
86
- def call_http name, request
87
- function = ::FunctionsFramework.global_registry[name]
135
+ def call_http name, request, globals: nil, logger: nil
136
+ globals ||= run_startup_tasks name, logger: logger, lenient: true
137
+ function = Testing.current_registry[name]
88
138
  case function&.type
89
139
  when :http
90
- Testing.interpret_response { function.new_call.call request }
140
+ Testing.interpret_response do
141
+ function.call request, globals: globals, logger: logger
142
+ end
91
143
  when nil
92
144
  raise "Unknown function name #{name}"
93
145
  else
@@ -99,15 +151,28 @@ module FunctionsFramework
99
151
  # Call the given event function for testing. The underlying function must
100
152
  # be of type :cloud_event`.
101
153
  #
154
+ # By default, the startup tasks will be run for the given function if they
155
+ # have not already been run. You can, however, disable running startup
156
+ # tasks by providing an explicit globals hash.
157
+ #
158
+ # By default, the {FunctionsFramework.logger} will be used, but you can
159
+ # override that by providing your own logger. In particular, to disable
160
+ # logging, you can pass `Logger.new(nil)`.
161
+ #
102
162
  # @param name [String] The name of the function to call
103
163
  # @param event [::CloudEvents::Event] The event to send
164
+ # @param globals [Hash] Do not run startup tasks, and instead provide the
165
+ # globals directly. Optional.
166
+ # @param logger [Logger] Use the given logger instead of the Functions
167
+ # Framework's global logger. Optional.
104
168
  # @return [nil]
105
169
  #
106
- def call_event name, event
107
- function = ::FunctionsFramework.global_registry[name]
170
+ def call_event name, event, globals: nil, logger: nil
171
+ globals ||= run_startup_tasks name, logger: logger, lenient: true
172
+ function = Testing.current_registry[name]
108
173
  case function&.type
109
174
  when :cloud_event
110
- function.new_call.call event
175
+ function.call event, globals: globals, logger: logger
111
176
  nil
112
177
  when nil
113
178
  raise "Unknown function name #{name}"
@@ -208,27 +273,49 @@ module FunctionsFramework
208
273
  extend self
209
274
 
210
275
  @testing_registries = {}
276
+ @main_globals = {}
211
277
  @mutex = ::Mutex.new
212
278
 
213
279
  class << self
214
280
  ## @private
215
281
  def load_for_testing path
216
282
  old_registry = ::FunctionsFramework.global_registry
217
- @mutex.synchronize do
218
- if @testing_registries.key? path
219
- ::FunctionsFramework.global_registry = @testing_registries[path]
220
- else
221
- new_registry = ::FunctionsFramework::Registry.new
222
- ::FunctionsFramework.global_registry = new_registry
223
- ::Kernel.load path
224
- @testing_registries[path] = new_registry
283
+ ::Thread.current[:functions_framework_testing_registry] =
284
+ @mutex.synchronize do
285
+ if @testing_registries.key? path
286
+ ::FunctionsFramework.global_registry = @testing_registries[path]
287
+ else
288
+ new_registry = ::FunctionsFramework::Registry.new
289
+ ::FunctionsFramework.global_registry = new_registry
290
+ ::Kernel.load path
291
+ @testing_registries[path] = new_registry
292
+ end
225
293
  end
226
- end
294
+ ::Thread.current[:functions_framework_testing_globals] = {}
227
295
  yield
228
296
  ensure
297
+ ::Thread.current[:functions_framework_testing_registry] = nil
298
+ ::Thread.current[:functions_framework_testing_globals] = nil
229
299
  ::FunctionsFramework.global_registry = old_registry
230
300
  end
231
301
 
302
+ ## @private
303
+ def current_registry
304
+ ::Thread.current[:functions_framework_testing_registry] ||
305
+ ::FunctionsFramework.global_registry
306
+ end
307
+
308
+ ## @private
309
+ def current_globals name, globals = nil
310
+ name = name.to_s
311
+ globals_by_name = ::Thread.current[:functions_framework_testing_globals] || @main_globals
312
+ if globals
313
+ globals_by_name[name] = globals
314
+ else
315
+ globals_by_name[name]
316
+ end
317
+ end
318
+
232
319
  ## @private
233
320
  def interpret_response
234
321
  response =
@@ -17,5 +17,5 @@ module FunctionsFramework
17
17
  # Version of the Ruby Functions Framework
18
18
  # @return [String]
19
19
  #
20
- VERSION = "0.5.0".freeze
20
+ VERSION = "0.7.1".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.5.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-09 00:00:00.000000000 Z
11
+ date: 2021-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cloud_events
@@ -52,7 +52,10 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.1'
55
- description: The Functions Framework implementation for Ruby.
55
+ description: The Functions Framework is an open source framework for writing lightweight,
56
+ portable Ruby functions that run in a serverless environment. Functions written
57
+ to this Framework will run on Google Cloud Functions, Google Cloud Run, or any other
58
+ Knative-based environment.
56
59
  email:
57
60
  - dazuma@google.com
58
61
  executables:
@@ -84,10 +87,10 @@ homepage: https://github.com/GoogleCloudPlatform/functions-framework-ruby
84
87
  licenses:
85
88
  - Apache-2.0
86
89
  metadata:
87
- changelog_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/blob/master/CHANGELOG.md
90
+ changelog_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.7.1/file.CHANGELOG.html
88
91
  source_code_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby
89
92
  bug_tracker_uri: https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues
90
- documentation_uri: https://rubydoc.info/gems/functions_framework/0.5.0
93
+ documentation_uri: https://googlecloudplatform.github.io/functions-framework-ruby/v0.7.1
91
94
  post_install_message:
92
95
  rdoc_options: []
93
96
  require_paths:
@@ -96,14 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
99
  requirements:
97
100
  - - ">="
98
101
  - !ruby/object:Gem::Version
99
- version: 2.4.0
102
+ version: 2.5.0
100
103
  required_rubygems_version: !ruby/object:Gem::Requirement
101
104
  requirements:
102
105
  - - ">="
103
106
  - !ruby/object:Gem::Version
104
107
  version: '0'
105
108
  requirements: []
106
- rubygems_version: 3.1.2
109
+ rubygems_version: 3.1.4
107
110
  signing_key:
108
111
  specification_version: 4
109
112
  summary: Functions Framework for Ruby