google-cloud-pubsub 2.3.2 → 2.7.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.
@@ -26,7 +26,10 @@ module Google
26
26
  ##
27
27
  # @private Represents the Pub/Sub service API, including IAM mixins.
28
28
  class Service
29
- attr_accessor :project, :credentials, :host, :timeout
29
+ attr_accessor :project
30
+ attr_accessor :credentials
31
+ attr_accessor :host
32
+ attr_accessor :timeout
30
33
  ###
31
34
  # The same client_id is used across all streaming pull connections that are created by this client. This is
32
35
  # intentional, as it indicates to the server that any guarantees, such as message ordering, made for a stream
@@ -46,49 +49,56 @@ module Google
46
49
 
47
50
  def subscriber
48
51
  return mocked_subscriber if mocked_subscriber
49
- @subscriber ||= begin
50
- V1::Subscriber::Client.new do |config|
51
- config.credentials = credentials if credentials
52
- config.timeout = timeout if timeout
53
- config.endpoint = host if host
54
- config.lib_name = "gccl"
55
- config.lib_version = Google::Cloud::PubSub::VERSION
56
- config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
57
- end
52
+ @subscriber ||= V1::Subscriber::Client.new do |config|
53
+ config.credentials = credentials if credentials
54
+ config.timeout = timeout if timeout
55
+ config.endpoint = host if host
56
+ config.lib_name = "gccl"
57
+ config.lib_version = Google::Cloud::PubSub::VERSION
58
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
58
59
  end
59
60
  end
60
61
  attr_accessor :mocked_subscriber
61
62
 
62
63
  def publisher
63
64
  return mocked_publisher if mocked_publisher
64
- @publisher ||= begin
65
- V1::Publisher::Client.new do |config|
66
- config.credentials = credentials if credentials
67
- config.timeout = timeout if timeout
68
- config.endpoint = host if host
69
- config.lib_name = "gccl"
70
- config.lib_version = Google::Cloud::PubSub::VERSION
71
- config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
72
- end
65
+ @publisher ||= V1::Publisher::Client.new do |config|
66
+ config.credentials = credentials if credentials
67
+ config.timeout = timeout if timeout
68
+ config.endpoint = host if host
69
+ config.lib_name = "gccl"
70
+ config.lib_version = Google::Cloud::PubSub::VERSION
71
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
73
72
  end
74
73
  end
75
74
  attr_accessor :mocked_publisher
76
75
 
77
76
  def iam
78
77
  return mocked_iam if mocked_iam
79
- @iam ||= begin
80
- V1::IAMPolicy::Client.new do |config|
81
- config.credentials = credentials if credentials
82
- config.timeout = timeout if timeout
83
- config.endpoint = host if host
84
- config.lib_name = "gccl"
85
- config.lib_version = Google::Cloud::PubSub::VERSION
86
- config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
87
- end
78
+ @iam ||= V1::IAMPolicy::Client.new do |config|
79
+ config.credentials = credentials if credentials
80
+ config.timeout = timeout if timeout
81
+ config.endpoint = host if host
82
+ config.lib_name = "gccl"
83
+ config.lib_version = Google::Cloud::PubSub::VERSION
84
+ config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
88
85
  end
89
86
  end
90
87
  attr_accessor :mocked_iam
91
88
 
89
+ def schemas
90
+ return mocked_schemas if mocked_schemas
91
+ @schemas ||= V1::SchemaService::Client.new do |config|
92
+ config.credentials = credentials if credentials
93
+ config.timeout = timeout if timeout
94
+ config.endpoint = host if host
95
+ config.lib_name = "gccl"
96
+ config.lib_version = Google::Cloud::PubSub::VERSION
97
+ config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
98
+ end
99
+ end
100
+ attr_accessor :mocked_schemas
101
+
92
102
  ##
93
103
  # Gets the configuration of a topic.
94
104
  # Since the topic only has the name attribute,
@@ -111,18 +121,35 @@ module Google
111
121
 
112
122
  ##
113
123
  # Creates the given topic with the given name.
114
- def create_topic topic_name, labels: nil, kms_key_name: nil, persistence_regions: nil, options: {}
124
+ def create_topic topic_name,
125
+ labels: nil,
126
+ kms_key_name: nil,
127
+ persistence_regions: nil,
128
+ schema_name: nil,
129
+ message_encoding: nil,
130
+ options: {}
115
131
  if persistence_regions
116
- message_storage_policy = {
132
+ message_storage_policy = Google::Cloud::PubSub::V1::MessageStoragePolicy.new(
117
133
  allowed_persistence_regions: Array(persistence_regions)
118
- }
134
+ )
135
+ end
136
+
137
+ if schema_name || message_encoding
138
+ unless schema_name && message_encoding
139
+ raise ArgumentError, "Schema settings must include both schema_name and message_encoding."
140
+ end
141
+ schema_settings = Google::Cloud::PubSub::V1::SchemaSettings.new(
142
+ schema: schema_path(schema_name),
143
+ encoding: message_encoding.to_s.upcase
144
+ )
119
145
  end
120
146
 
121
147
  publisher.create_topic \
122
148
  name: topic_path(topic_name, options),
123
149
  labels: labels,
124
150
  kms_key_name: kms_key_name,
125
- message_storage_policy: message_storage_policy
151
+ message_storage_policy: message_storage_policy,
152
+ schema_settings: schema_settings
126
153
  end
127
154
 
128
155
  def update_topic topic_obj, *fields
@@ -294,6 +321,89 @@ module Google
294
321
  end
295
322
  end
296
323
 
324
+ ##
325
+ # Lists schemas in the current (or given) project.
326
+ # @param view [String, Symbol, nil] Possible values:
327
+ # * `BASIC` - Include the name and type of the schema, but not the definition.
328
+ # * `FULL` - Include all Schema object fields.
329
+ #
330
+ def list_schemas view, options = {}
331
+ schema_view = Google::Cloud::PubSub::V1::SchemaView.const_get view.to_s.upcase
332
+ paged_enum = schemas.list_schemas parent: project_path(options),
333
+ view: schema_view,
334
+ page_size: options[:max],
335
+ page_token: options[:token]
336
+
337
+ paged_enum.response
338
+ end
339
+
340
+ ##
341
+ # Creates a schema in the current (or given) project.
342
+ def create_schema schema_id, type, definition, options = {}
343
+ schema = Google::Cloud::PubSub::V1::Schema.new(
344
+ type: type,
345
+ definition: definition
346
+ )
347
+ schemas.create_schema parent: project_path(options),
348
+ schema: schema,
349
+ schema_id: schema_id
350
+ end
351
+
352
+ ##
353
+ # Gets the details of a schema.
354
+ # @param view [String, Symbol, nil] The set of fields to return in the response. Possible values:
355
+ # * `BASIC` - Include the name and type of the schema, but not the definition.
356
+ # * `FULL` - Include all Schema object fields.
357
+ #
358
+ def get_schema schema_name, view, options = {}
359
+ schema_view = Google::Cloud::PubSub::V1::SchemaView.const_get view.to_s.upcase
360
+ schemas.get_schema name: schema_path(schema_name, options),
361
+ view: schema_view
362
+ end
363
+
364
+ ##
365
+ # Delete a schema.
366
+ def delete_schema schema_name
367
+ schemas.delete_schema name: schema_path(schema_name)
368
+ end
369
+
370
+ ##
371
+ # Validate the definition string intended for a schema.
372
+ def validate_schema type, definition, options = {}
373
+ schema = Google::Cloud::PubSub::V1::Schema.new(
374
+ type: type,
375
+ definition: definition
376
+ )
377
+ schemas.validate_schema parent: project_path(options),
378
+ schema: schema
379
+ end
380
+
381
+ ##
382
+ # Validates a message against a schema.
383
+ #
384
+ # @param message_data [String] Message to validate against the provided `schema_spec`.
385
+ # @param message_encoding [Google::Cloud::PubSub::V1::Encoding] The encoding expected for messages.
386
+ # @param schema_name [String] Name of the schema against which to validate.
387
+ # @param project [String] Name of the project if not the default project.
388
+ # @param type [String] Ad-hoc schema type against which to validate.
389
+ # @param definition [String] Ad-hoc schema definition against which to validate.
390
+ #
391
+ def validate_message message_data, message_encoding, schema_name: nil, project: nil, type: nil, definition: nil
392
+ if type && definition
393
+ schema = Google::Cloud::PubSub::V1::Schema.new(
394
+ type: type,
395
+ definition: definition
396
+ )
397
+ end
398
+ schemas.validate_message parent: project_path(project: project),
399
+ name: schema_path(schema_name),
400
+ schema: schema,
401
+ message: message_data,
402
+ encoding: message_encoding
403
+ end
404
+
405
+ # Helper methods
406
+
297
407
  def get_topic_policy topic_name, options = {}
298
408
  iam.get_iam_policy resource: topic_path(topic_name, options)
299
409
  end
@@ -338,6 +448,11 @@ module Google
338
448
  "#{project_path options}/snapshots/#{snapshot_name}"
339
449
  end
340
450
 
451
+ def schema_path schema_name, options = {}
452
+ return schema_name if schema_name.nil? || schema_name.to_s.include?("/")
453
+ "#{project_path options}/schemas/#{schema_name}"
454
+ end
455
+
341
456
  def inspect
342
457
  "#<#{self.class.name} (#{@project})>"
343
458
  end
@@ -15,6 +15,7 @@
15
15
 
16
16
  require "google/cloud/errors"
17
17
  require "google/cloud/pubsub/snapshot/list"
18
+ require "google/cloud/pubsub/v1"
18
19
 
19
20
  module Google
20
21
  module Cloud
@@ -125,12 +125,12 @@ module Google
125
125
  # puts snapshot.name
126
126
  # end
127
127
  #
128
- def all request_limit: nil
128
+ def all request_limit: nil, &block
129
129
  request_limit = request_limit.to_i if request_limit
130
130
  return enum_for :all, request_limit: request_limit unless block_given?
131
131
  results = self
132
132
  loop do
133
- results.each { |r| yield r }
133
+ results.each(&block)
134
134
  if request_limit
135
135
  request_limit -= 1
136
136
  break if request_limit.negative?
@@ -64,8 +64,13 @@ module Google
64
64
  class Subscriber
65
65
  include MonitorMixin
66
66
 
67
- attr_reader :subscription_name, :callback, :deadline, :streams, :message_ordering, :callback_threads,
68
- :push_threads
67
+ attr_reader :subscription_name
68
+ attr_reader :callback
69
+ attr_reader :deadline
70
+ attr_reader :streams
71
+ attr_reader :message_ordering
72
+ attr_reader :callback_threads
73
+ attr_reader :push_threads
69
74
 
70
75
  ##
71
76
  # @private Implementation attributes.
@@ -123,28 +128,21 @@ module Google
123
128
 
124
129
  ##
125
130
  # Immediately stops the subscriber. No new messages will be pulled from
126
- # the subscription. All actions taken on received messages that have not
127
- # yet been sent to the API will be sent to the API. All received but
128
- # unprocessed messages will be released back to the API and redelivered.
129
- # Use {#wait!} to block until the subscriber is fully stopped and all
130
- # received messages have been processed or released.
131
+ # the subscription. Use {#wait!} to block until all received messages have
132
+ # been processed or released: All actions taken on received messages that
133
+ # have not yet been sent to the API will be sent to the API. All received
134
+ # but unprocessed messages will be released back to the API and redelivered.
131
135
  #
132
136
  # @return [Subscriber] returns self so calls can be chained.
133
137
  #
134
138
  def stop
135
- stop_pool = synchronize do
139
+ synchronize do
136
140
  @started = false
137
141
  @stopped = true
138
-
139
- @stream_pool.map do |stream|
140
- Thread.new { stream.stop }
141
- end
142
+ @stream_pool.map(&:stop)
143
+ wait_stop_buffer_thread!
144
+ self
142
145
  end
143
- stop_pool.map(&:join)
144
- # Stop the buffer after the streams are all stopped
145
- synchronize { @buffer.stop }
146
-
147
- self
148
146
  end
149
147
 
150
148
  ##
@@ -162,13 +160,8 @@ module Google
162
160
  # @return [Subscriber] returns self so calls can be chained.
163
161
  #
164
162
  def wait! timeout = nil
165
- wait_pool = synchronize do
166
- @stream_pool.map do |stream|
167
- Thread.new { stream.wait! timeout }
168
- end
169
- end
170
- wait_pool.map(&:join)
171
-
163
+ wait_stop_buffer_thread!
164
+ @wait_stop_buffer_thread.join timeout
172
165
  self
173
166
  end
174
167
 
@@ -374,6 +367,18 @@ module Google
374
367
 
375
368
  protected
376
369
 
370
+ ##
371
+ # Starts a new thread to call wait! (blocking) on each Stream and then stop the TimedUnaryBuffer.
372
+ def wait_stop_buffer_thread!
373
+ synchronize do
374
+ @wait_stop_buffer_thread ||= Thread.new do
375
+ @stream_pool.map(&:wait!)
376
+ # Shutdown the buffer TimerTask (and flush the buffer) after the streams are all stopped.
377
+ @buffer.stop
378
+ end
379
+ end
380
+ end
381
+
377
382
  def coerce_inventory inventory
378
383
  @inventory = inventory
379
384
  if @inventory.is_a? Hash
@@ -30,8 +30,12 @@ module Google
30
30
 
31
31
  include MonitorMixin
32
32
 
33
- attr_reader :stream, :limit, :bytesize, :extension, :max_duration_per_lease_extension,
34
- :use_legacy_flow_control
33
+ attr_reader :stream
34
+ attr_reader :limit
35
+ attr_reader :bytesize
36
+ attr_reader :extension
37
+ attr_reader :max_duration_per_lease_extension
38
+ attr_reader :use_legacy_flow_control
35
39
 
36
40
  def initialize stream, limit:, bytesize:, extension:, max_duration_per_lease_extension:,
37
41
  use_legacy_flow_control:
@@ -58,7 +58,7 @@ module Google
58
58
  @paused = nil
59
59
  @pause_cond = new_cond
60
60
 
61
- @inventory = Inventory.new self, @subscriber.stream_inventory
61
+ @inventory = Inventory.new self, **@subscriber.stream_inventory
62
62
 
63
63
  @sequencer = Sequencer.new(&method(:perform_callback_async)) if subscriber.message_ordering
64
64
 
@@ -25,7 +25,8 @@ module Google
25
25
  class TimedUnaryBuffer
26
26
  include MonitorMixin
27
27
 
28
- attr_reader :max_bytes, :interval
28
+ attr_reader :max_bytes
29
+ attr_reader :interval
29
30
 
30
31
  def initialize subscriber, max_bytes: 500_000, interval: 1.0
31
32
  super() # to init MonitorMixin
@@ -138,7 +139,7 @@ module Google
138
139
  end
139
140
 
140
141
  groups = prev_reg.each_pair.group_by { |_ack_id, delay| delay }
141
- req_hash = Hash[groups.map { |k, v| [k, v.map(&:first)] }]
142
+ req_hash = groups.transform_values { |v| v.map(&:first) }
142
143
 
143
144
  requests = { acknowledge: [] }
144
145
  ack_ids = Array(req_hash.delete(:ack)) # ack has no deadline set
@@ -215,11 +216,9 @@ module Google
215
216
 
216
217
  def add_future pool
217
218
  Concurrent::Promises.future_on pool do
218
- begin
219
- yield
220
- rescue StandardError => e
221
- error! e
222
- end
219
+ yield
220
+ rescue StandardError => e
221
+ error! e
223
222
  end
224
223
  end
225
224
  end
@@ -733,19 +733,28 @@ module Google
733
733
  end
734
734
 
735
735
  ##
736
- # Pulls messages from the server. Returns an empty list if there are no
737
- # messages available in the backlog. Raises an ApiError with status
738
- # `UNAVAILABLE` if there are too many concurrent pull requests pending
739
- # for the given subscription.
736
+ # Pulls messages from the server, blocking until messages are available
737
+ # when called with the `immediate: false` option, which is recommended
738
+ # to avoid adverse impacts on the performance of pull operations.
739
+ #
740
+ # Raises an API error with status `UNAVAILABLE` if there are too many
741
+ # concurrent pull requests pending for the given subscription.
740
742
  #
741
743
  # See also {#listen} for the preferred way to process messages as they
742
744
  # become available.
743
745
  #
744
- # @param [Boolean] immediate When `true` the system will respond
745
- # immediately even if it is not able to return messages. When `false`
746
- # the system is allowed to wait until it can return least one message.
747
- # No messages are returned when a request times out. The default value
748
- # is `true`.
746
+ # @param [Boolean] immediate Whether to return immediately or block until
747
+ # messages are available.
748
+ #
749
+ # **Warning:** The default value of this field is `true`. However, sending
750
+ # `true` is discouraged because it adversely impacts the performance of
751
+ # pull operations. We recommend that users always explicitly set this field
752
+ # to `false`.
753
+ #
754
+ # If this field set to `true`, the system will respond immediately
755
+ # even if it there are no messages available to return in the pull
756
+ # response. Otherwise, the system may wait (for a bounded amount of time)
757
+ # until at least one message is available, rather than returning no messages.
749
758
  #
750
759
  # See also {#listen} for the preferred way to process messages as they
751
760
  # become available.
@@ -755,31 +764,24 @@ module Google
755
764
  #
756
765
  # @return [Array<Google::Cloud::PubSub::ReceivedMessage>]
757
766
  #
758
- # @example
767
+ # @example The `immediate: false` option is now recommended to avoid adverse impacts on pull operations:
759
768
  # require "google/cloud/pubsub"
760
769
  #
761
770
  # pubsub = Google::Cloud::PubSub.new
762
771
  #
763
772
  # sub = pubsub.subscription "my-topic-sub"
764
- # sub.pull.each { |received_message| received_message.acknowledge! }
765
- #
766
- # @example A maximum number of messages returned can also be specified:
767
- # require "google/cloud/pubsub"
768
- #
769
- # pubsub = Google::Cloud::PubSub.new
770
- #
771
- # sub = pubsub.subscription "my-topic-sub"
772
- # sub.pull(max: 10).each do |received_message|
773
+ # received_messages = sub.pull immediate: false
774
+ # received_messages.each do |received_message|
773
775
  # received_message.acknowledge!
774
776
  # end
775
777
  #
776
- # @example The call can block until messages are available:
778
+ # @example A maximum number of messages returned can also be specified:
777
779
  # require "google/cloud/pubsub"
778
780
  #
779
781
  # pubsub = Google::Cloud::PubSub.new
780
782
  #
781
783
  # sub = pubsub.subscription "my-topic-sub"
782
- # received_messages = sub.pull immediate: false
784
+ # received_messages = sub.pull immediate: false, max: 10
783
785
  # received_messages.each do |received_message|
784
786
  # received_message.acknowledge!
785
787
  # end
@@ -1010,7 +1012,7 @@ module Google
1010
1012
  # pubsub = Google::Cloud::PubSub.new
1011
1013
  #
1012
1014
  # sub = pubsub.subscription "my-topic-sub"
1013
- # received_messages = sub.pull
1015
+ # received_messages = sub.pull immediate: false
1014
1016
  # sub.acknowledge received_messages
1015
1017
  #
1016
1018
  def acknowledge *messages
@@ -1045,7 +1047,7 @@ module Google
1045
1047
  # pubsub = Google::Cloud::PubSub.new
1046
1048
  #
1047
1049
  # sub = pubsub.subscription "my-topic-sub"
1048
- # received_messages = sub.pull
1050
+ # received_messages = sub.pull immediate: false
1049
1051
  # sub.modify_ack_deadline 120, received_messages
1050
1052
  #
1051
1053
  def modify_ack_deadline new_deadline, *messages
@@ -1143,7 +1145,7 @@ module Google
1143
1145
  #
1144
1146
  # snapshot = sub.create_snapshot
1145
1147
  #
1146
- # received_messages = sub.pull
1148
+ # received_messages = sub.pull immediate: false
1147
1149
  # sub.acknowledge received_messages
1148
1150
  #
1149
1151
  # sub.seek snapshot
@@ -1156,7 +1158,7 @@ module Google
1156
1158
  #
1157
1159
  # time = Time.now
1158
1160
  #
1159
- # received_messages = sub.pull
1161
+ # received_messages = sub.pull immediate: false
1160
1162
  # sub.acknowledge received_messages
1161
1163
  #
1162
1164
  # sub.seek time