gcs_put 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.standard.yml +3 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +139 -0
- data/Rakefile +27 -0
- data/gcs_put.gemspec +41 -0
- data/lib/gcs_put/byte_chunker.rb +59 -0
- data/lib/gcs_put/resumable_upload.rb +227 -0
- data/lib/gcs_put/signer.rb +36 -0
- data/lib/gcs_put/transport/faraday.rb +56 -0
- data/lib/gcs_put/transport/net_http.rb +64 -0
- data/lib/gcs_put/transport.rb +38 -0
- data/lib/gcs_put/version.rb +6 -0
- data/lib/gcs_put.rb +62 -0
- data/rbi/gcs_put.rbi +399 -0
- data/sig/gcs_put.rbs +345 -0
- metadata +206 -0
data/sig/gcs_put.rbs
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# The Ruby GCP SDK can only upload things it can measure in advance. GCS itself has supported
|
|
2
|
+
# resumable uploads of unknown size for ages though. This gem gives you a writable object
|
|
3
|
+
# which chops what you write to it into correctly sized chunks and PUTs them into a
|
|
4
|
+
# resumable upload session, so you never need to know the size up front.
|
|
5
|
+
module GCSPut
|
|
6
|
+
extend Forwardable
|
|
7
|
+
CHUNK_SIZE_UNIT: untyped
|
|
8
|
+
DEFAULT_CHUNK_SIZE: untyped
|
|
9
|
+
VERSION: untyped
|
|
10
|
+
|
|
11
|
+
# Base class for everything the gem raises
|
|
12
|
+
class Error < StandardError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Raised by transports for failures worth retrying - connection resets, timeouts and the like
|
|
16
|
+
class TransientError < GCSPut::Error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Raised when GCS answers a chunk PUT with something we cannot recover from
|
|
20
|
+
class UploadFailed < GCSPut::Error
|
|
21
|
+
# _@param_ `message`
|
|
22
|
+
#
|
|
23
|
+
# _@param_ `response`
|
|
24
|
+
def initialize: (String message, ?response: GCSPut::Transport::Response?) -> void
|
|
25
|
+
|
|
26
|
+
# _@return_ — the response which caused the failure, if there was one
|
|
27
|
+
attr_reader response: GCSPut::Transport::Response?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Supplies the `issuer` and `signer` that `Google::Cloud::Storage::File#signed_url` needs
|
|
31
|
+
# when there is no private key around to sign with
|
|
32
|
+
module Signer
|
|
33
|
+
# When running on GCE, GKE, Cloud Run etc. under a service account there is no private key
|
|
34
|
+
# on the box to sign the URL with. The SDK then needs an `issuer` (the account email) and a
|
|
35
|
+
# `signer` lambda which asks the IAM credentials API to sign for us. For that to be allowed the
|
|
36
|
+
# service account must have `roles/iam.serviceAccountTokenCreator` on itself.
|
|
37
|
+
# Lifted from https://github.com/googleapis/google-cloud-ruby/issues/13307#issuecomment-1894546343
|
|
38
|
+
#
|
|
39
|
+
# _@return_ — either `{issuer:, signer:}` or an empty hash when not on compute engine
|
|
40
|
+
def self.url_issuer_and_signer: () -> ::Hash[untyped, untyped]
|
|
41
|
+
|
|
42
|
+
# _@param_ `service_account_email`
|
|
43
|
+
#
|
|
44
|
+
# _@return_ — a lambda which takes the string to sign and returns the signature
|
|
45
|
+
def self.iam_signer_for: (String service_account_email) -> Proc
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The uploader needs two HTTP verbs and does not care what performs them. A transport is any
|
|
49
|
+
# object with `put(uri, body, headers)`, `post(uri, body, headers)` and `close`, where the verbs
|
|
50
|
+
# return a `Transport::Response`. Failures worth retrying (connection resets, timeouts and the
|
|
51
|
+
# like) must surface as `GCSPut::TransientError` so the uploader knows it may query
|
|
52
|
+
# the session and carry on. Anything else is allowed to propagate.
|
|
53
|
+
module Transport
|
|
54
|
+
# What a transport hands back from `put` and `post`
|
|
55
|
+
class Response
|
|
56
|
+
# sord duck - #to_i looks like a duck type with an equivalent RBS interface, replacing with _ToI
|
|
57
|
+
# sord duck - #to_h looks like a duck type, replacing with untyped
|
|
58
|
+
# sord duck - #to_s looks like a duck type with an equivalent RBS interface, replacing with _ToS
|
|
59
|
+
# _@param_ `status`
|
|
60
|
+
#
|
|
61
|
+
# _@param_ `headers` — header names in any case
|
|
62
|
+
#
|
|
63
|
+
# _@param_ `body`
|
|
64
|
+
def initialize: (_ToI status, untyped headers, _ToS body) -> void
|
|
65
|
+
|
|
66
|
+
# _@param_ `name` — header name in any case
|
|
67
|
+
def []: (String name) -> String?
|
|
68
|
+
|
|
69
|
+
# _@return_ — the HTTP status code
|
|
70
|
+
attr_reader status: Integer
|
|
71
|
+
|
|
72
|
+
# _@return_ — the headers, with lowercased names
|
|
73
|
+
attr_reader headers: ::Hash[String, String]
|
|
74
|
+
|
|
75
|
+
# _@return_ — the body, empty if there was none
|
|
76
|
+
attr_reader body: String
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Uses a Faraday connection, so you can pick the adapter, timeouts and instrumentation yourself.
|
|
80
|
+
# Check the bytes arrive intact with whatever adapter you choose - we send a `Content-MD5` with
|
|
81
|
+
# every chunk precisely because httpx 1.4.0 used to mangle request bodies,
|
|
82
|
+
# see https://gitlab.com/os85/httpx/-/issues/338
|
|
83
|
+
class Faraday
|
|
84
|
+
TRANSIENT_ERRORS: untyped
|
|
85
|
+
|
|
86
|
+
# sord warn - Faraday::Connection wasn't able to be resolved to a constant in this project
|
|
87
|
+
# _@param_ `connection` — a connection of your own, or `nil` to get a default one
|
|
88
|
+
def initialize: (?Faraday::Connection? connection) -> void
|
|
89
|
+
|
|
90
|
+
# sord warn - URI::Generic wasn't able to be resolved to a constant in this project
|
|
91
|
+
# _@param_ `uri`
|
|
92
|
+
#
|
|
93
|
+
# _@param_ `body`
|
|
94
|
+
#
|
|
95
|
+
# _@param_ `headers`
|
|
96
|
+
def put: (URI::Generic uri, String body, ::Hash[String, String] headers) -> GCSPut::Transport::Response
|
|
97
|
+
|
|
98
|
+
# sord warn - URI::Generic wasn't able to be resolved to a constant in this project
|
|
99
|
+
# _@param_ `uri`
|
|
100
|
+
#
|
|
101
|
+
# _@param_ `body`
|
|
102
|
+
#
|
|
103
|
+
# _@param_ `headers`
|
|
104
|
+
def post: (URI::Generic uri, String body, ::Hash[String, String] headers) -> GCSPut::Transport::Response
|
|
105
|
+
|
|
106
|
+
# A connection you passed in is yours to close, we only close the one we made
|
|
107
|
+
def close: () -> void
|
|
108
|
+
|
|
109
|
+
# sord omit - no YARD type given for "verb", using untyped
|
|
110
|
+
# sord omit - no YARD type given for "uri", using untyped
|
|
111
|
+
# sord omit - no YARD type given for "body", using untyped
|
|
112
|
+
# sord omit - no YARD type given for "headers", using untyped
|
|
113
|
+
# sord omit - no YARD return type given, using untyped
|
|
114
|
+
def request: (
|
|
115
|
+
untyped verb,
|
|
116
|
+
untyped uri,
|
|
117
|
+
untyped body,
|
|
118
|
+
untyped headers
|
|
119
|
+
) -> untyped
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Keeps one connection per host open for the duration of the upload
|
|
123
|
+
class NetHTTP
|
|
124
|
+
TRANSIENT_ERRORS: untyped
|
|
125
|
+
|
|
126
|
+
# _@param_ `http_options` — forwarded to `Net::HTTP.start` (`open_timeout:`, `read_timeout:`...)
|
|
127
|
+
def initialize: (**::Hash[untyped, untyped] http_options) -> void
|
|
128
|
+
|
|
129
|
+
# sord warn - URI::Generic wasn't able to be resolved to a constant in this project
|
|
130
|
+
# _@param_ `uri`
|
|
131
|
+
#
|
|
132
|
+
# _@param_ `body`
|
|
133
|
+
#
|
|
134
|
+
# _@param_ `headers`
|
|
135
|
+
def put: (URI::Generic uri, String body, ::Hash[String, String] headers) -> GCSPut::Transport::Response
|
|
136
|
+
|
|
137
|
+
# sord warn - URI::Generic wasn't able to be resolved to a constant in this project
|
|
138
|
+
# _@param_ `uri`
|
|
139
|
+
#
|
|
140
|
+
# _@param_ `body`
|
|
141
|
+
#
|
|
142
|
+
# _@param_ `headers`
|
|
143
|
+
def post: (URI::Generic uri, String body, ::Hash[String, String] headers) -> GCSPut::Transport::Response
|
|
144
|
+
|
|
145
|
+
# Closes the kept connections. Safe to call repeatedly, the next request reopens as needed
|
|
146
|
+
def close: () -> void
|
|
147
|
+
|
|
148
|
+
# sord omit - no YARD type given for "request_class", using untyped
|
|
149
|
+
# sord omit - no YARD type given for "uri", using untyped
|
|
150
|
+
# sord omit - no YARD type given for "body", using untyped
|
|
151
|
+
# sord omit - no YARD type given for "headers", using untyped
|
|
152
|
+
# sord omit - no YARD return type given, using untyped
|
|
153
|
+
def request: (
|
|
154
|
+
untyped request_class,
|
|
155
|
+
untyped uri,
|
|
156
|
+
untyped body,
|
|
157
|
+
untyped headers
|
|
158
|
+
) -> untyped
|
|
159
|
+
|
|
160
|
+
# sord omit - no YARD type given for "uri", using untyped
|
|
161
|
+
# sord omit - no YARD return type given, using untyped
|
|
162
|
+
def connection_for: (untyped uri) -> untyped
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Chops an arbitrary stream of writes into evenly sized chunks. Every chunk except the last
|
|
167
|
+
# will be exactly `chunk_size` bytes, and the last one can be anything from 0 bytes up to and
|
|
168
|
+
# including `chunk_size`. A chunk which fills up exactly is held back until `finish` so that
|
|
169
|
+
# a stream ending on a chunk boundary still delivers that chunk flagged as the last one.
|
|
170
|
+
#
|
|
171
|
+
# chunker = ByteChunker.new(chunk_size: 3) { |bytes, is_last| puts [bytes, is_last].inspect }
|
|
172
|
+
# chunker << "ab" << "cdefg" # => ["abc", false], ["def", false]
|
|
173
|
+
# chunker.finish # => ["g", true]
|
|
174
|
+
class ByteChunker
|
|
175
|
+
# _@param_ `chunk_size` — the size that every chunk except the last must have
|
|
176
|
+
def initialize: (chunk_size: Integer) -> void
|
|
177
|
+
|
|
178
|
+
# _@param_ `bin_str` — the bytes to append
|
|
179
|
+
def <<: (String bin_str) -> self
|
|
180
|
+
|
|
181
|
+
# _@param_ `bin_str` — the bytes to append
|
|
182
|
+
#
|
|
183
|
+
# _@return_ — the number of bytes appended, like `IO#write`
|
|
184
|
+
def write: (String bin_str) -> Integer
|
|
185
|
+
|
|
186
|
+
# Delivers whatever is left in the buffer as the last chunk. The last chunk
|
|
187
|
+
# gets delivered even when empty - it is what closes the upload
|
|
188
|
+
def finish: () -> void
|
|
189
|
+
|
|
190
|
+
def deliver_full_chunks: () -> void
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# A writable object which streams what you write into a GCS resumable upload session using
|
|
194
|
+
# ranged PUTs, see https://cloud.google.com/storage/docs/performing-resumable-uploads#chunked-upload
|
|
195
|
+
# You do not need to know the size of the output in advance. You do need to `finish` the upload,
|
|
196
|
+
# since it is the last PUT (with the total size filled in) which closes the GCS object.
|
|
197
|
+
#
|
|
198
|
+
# gcs_file = bucket.file("upload.bin", skip_lookup: true)
|
|
199
|
+
# GCSPut.with_gcs_file(gcs_file) do |io|
|
|
200
|
+
# io.write("Hello resumable")
|
|
201
|
+
# 20.times { io.write(Random.bytes(1024 * 1024)) }
|
|
202
|
+
# end
|
|
203
|
+
#
|
|
204
|
+
# Every chunk is retried from the byte offset GCS reports as persisted, so a chunk which
|
|
205
|
+
# only partially made it over the wire gets topped up rather than resent from the start.
|
|
206
|
+
#
|
|
207
|
+
# Starting the session needs a signed POST URL, and signing needs a private key. Under
|
|
208
|
+
# workload identity there is no key, so we fall back to the IAM signBlob API - which means
|
|
209
|
+
# the service account must hold `roles/iam.serviceAccountTokenCreator` on itself. See
|
|
210
|
+
# https://github.com/googleapis/google-cloud-ruby/issues/13307
|
|
211
|
+
class ResumableUpload
|
|
212
|
+
extend Forwardable
|
|
213
|
+
|
|
214
|
+
# Appends bytes to the upload, sending out a chunk whenever one fills up
|
|
215
|
+
#
|
|
216
|
+
# _@param_ `bin_str`
|
|
217
|
+
#
|
|
218
|
+
# _@return_ — the number of bytes appended, like `IO#write`
|
|
219
|
+
def write: (String bin_str) -> Integer
|
|
220
|
+
|
|
221
|
+
# Appends bytes to the upload, sending out a chunk whenever one fills up
|
|
222
|
+
#
|
|
223
|
+
# _@param_ `bin_str`
|
|
224
|
+
def <<: (String bin_str) -> self
|
|
225
|
+
|
|
226
|
+
# sord warn - Google::Cloud::Storage::File wasn't able to be resolved to a constant in this project
|
|
227
|
+
# sord duck - #put looks like a duck type, replacing with untyped
|
|
228
|
+
# sord duck - #post looks like a duck type, replacing with untyped
|
|
229
|
+
# sord duck - #close looks like a duck type, replacing with untyped
|
|
230
|
+
# Signs a session start URL for the object, starts the session and returns the upload.
|
|
231
|
+
# With a block, yields the upload, finishes it once the block returns and returns the total size.
|
|
232
|
+
#
|
|
233
|
+
# _@param_ `gcs_file` — the object to upload into, does not need to exist yet
|
|
234
|
+
#
|
|
235
|
+
# _@param_ `content_type` — the content type of the resulting object
|
|
236
|
+
#
|
|
237
|
+
# _@param_ `transport` — see `GCSPut::Transport`
|
|
238
|
+
#
|
|
239
|
+
# _@param_ `signed_url_options` — passed to `gcs_file.signed_url`, see `Signer.url_issuer_and_signer`
|
|
240
|
+
#
|
|
241
|
+
# _@param_ `options` — see {#initialize}
|
|
242
|
+
#
|
|
243
|
+
# _@return_ — the upload, or the total size when given a block
|
|
244
|
+
def self.with_gcs_file: (
|
|
245
|
+
Google::Cloud::Storage::File gcs_file,
|
|
246
|
+
?content_type: String,
|
|
247
|
+
?transport: untyped,
|
|
248
|
+
?signed_url_options: ::Hash[untyped, untyped],
|
|
249
|
+
**::Hash[untyped, untyped] options
|
|
250
|
+
) -> (GCSPut::ResumableUpload | Integer)
|
|
251
|
+
|
|
252
|
+
# sord duck - #put looks like a duck type, replacing with untyped
|
|
253
|
+
# sord duck - #post looks like a duck type, replacing with untyped
|
|
254
|
+
# sord duck - #close looks like a duck type, replacing with untyped
|
|
255
|
+
# Starts a session from a signed POST URL (one with `x-goog-resumable: start` among its signed headers)
|
|
256
|
+
# and returns the upload, see https://cloud.google.com/storage/docs/performing-resumable-uploads#initiate-session
|
|
257
|
+
# With a block, yields the upload, finishes it once the block returns and returns the total size.
|
|
258
|
+
#
|
|
259
|
+
# _@param_ `signed_post_url`
|
|
260
|
+
#
|
|
261
|
+
# _@param_ `content_type` — must match the content type the URL was signed with
|
|
262
|
+
#
|
|
263
|
+
# _@param_ `transport` — see `GCSPut::Transport`
|
|
264
|
+
#
|
|
265
|
+
# _@param_ `options` — see {#initialize}
|
|
266
|
+
#
|
|
267
|
+
# _@return_ — the upload, or the total size when given a block
|
|
268
|
+
def self.with_signed_post_url: (
|
|
269
|
+
String signed_post_url,
|
|
270
|
+
?content_type: String,
|
|
271
|
+
?transport: untyped,
|
|
272
|
+
**::Hash[untyped, untyped] options
|
|
273
|
+
) -> (GCSPut::ResumableUpload | Integer)
|
|
274
|
+
|
|
275
|
+
# Wraps an already started session. With a block, yields the upload, finishes it once
|
|
276
|
+
# the block returns and returns the total size.
|
|
277
|
+
#
|
|
278
|
+
# _@param_ `session_url` — the `Location` returned by the session start
|
|
279
|
+
#
|
|
280
|
+
# _@param_ `options` — see {#initialize}
|
|
281
|
+
#
|
|
282
|
+
# _@return_ — the upload, or the total size when given a block
|
|
283
|
+
def self.with_session_url: (String session_url, **::Hash[untyped, untyped] options) -> (GCSPut::ResumableUpload | Integer)
|
|
284
|
+
|
|
285
|
+
# sord duck - #put looks like a duck type, replacing with untyped
|
|
286
|
+
# sord duck - #post looks like a duck type, replacing with untyped
|
|
287
|
+
# sord duck - #close looks like a duck type, replacing with untyped
|
|
288
|
+
# Prefer the `with_*` factories. This does no HTTP by itself, the first request goes out
|
|
289
|
+
# once a chunk fills up or `finish` gets called
|
|
290
|
+
#
|
|
291
|
+
# _@param_ `session_url` — the `Location` returned by the session start
|
|
292
|
+
#
|
|
293
|
+
# _@param_ `chunk_size` — must be a multiple of 256 KiB
|
|
294
|
+
#
|
|
295
|
+
# _@param_ `content_type` — must match the content type the session was started with
|
|
296
|
+
#
|
|
297
|
+
# _@param_ `max_attempts` — how many times a single chunk may be sent before giving up
|
|
298
|
+
#
|
|
299
|
+
# _@param_ `transport` — see `GCSPut::Transport`
|
|
300
|
+
def initialize: (
|
|
301
|
+
String session_url,
|
|
302
|
+
?chunk_size: Integer,
|
|
303
|
+
?content_type: String,
|
|
304
|
+
?max_attempts: Integer,
|
|
305
|
+
?transport: untyped
|
|
306
|
+
) -> void
|
|
307
|
+
|
|
308
|
+
# Sends the remaining buffered bytes as the final chunk and closes the GCS object.
|
|
309
|
+
# Also available as `close` so that writers which close their underlying IO, like
|
|
310
|
+
# `Zlib::GzipWriter`, finish the upload for you
|
|
311
|
+
#
|
|
312
|
+
# _@return_ — the total number of bytes uploaded
|
|
313
|
+
def finish: () -> Integer
|
|
314
|
+
|
|
315
|
+
# _@param_ `chunk`
|
|
316
|
+
#
|
|
317
|
+
# _@param_ `is_last`
|
|
318
|
+
def upload_chunk: (String chunk, bool is_last) -> void
|
|
319
|
+
|
|
320
|
+
# _@param_ `body`
|
|
321
|
+
#
|
|
322
|
+
# _@param_ `from`
|
|
323
|
+
#
|
|
324
|
+
# _@param_ `total` — the total size, or "*" while still unknown
|
|
325
|
+
def put_bytes: (String body, from: Integer, total: (Integer | String)) -> GCSPut::Transport::Response
|
|
326
|
+
|
|
327
|
+
# Asks GCS how much of the upload it has and updates `bytes_persisted` accordingly, see
|
|
328
|
+
# https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check
|
|
329
|
+
# A status check which fails itself just leaves us with what we already knew
|
|
330
|
+
#
|
|
331
|
+
# _@return_ — whether the session turned out to be finalized already
|
|
332
|
+
def sync_with_session_finds_it_finalized?: () -> bool
|
|
333
|
+
|
|
334
|
+
# The `Range` header is "bytes=0-N" with N being the last persisted byte, and absent if nothing persisted yet
|
|
335
|
+
#
|
|
336
|
+
# _@param_ `response`
|
|
337
|
+
def persisted_offset_from: (GCSPut::Transport::Response response) -> Integer
|
|
338
|
+
|
|
339
|
+
# _@return_ — the session URL, valid for a week and usable from any process
|
|
340
|
+
attr_reader session_url: String
|
|
341
|
+
|
|
342
|
+
# _@return_ — the number of bytes GCS has confirmed as persisted so far
|
|
343
|
+
attr_reader bytes_persisted: Integer
|
|
344
|
+
end
|
|
345
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gcs_put
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Julik Tarkhanov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-10-05 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: google-cloud-storage
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.40'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.40'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: minitest
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: webmock
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: faraday
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: magic_frozen_string_literal
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: standard
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: 1.35.1
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: 1.35.1
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: yard
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.9'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.9'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: sord
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: redcarpet
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
152
|
+
description: Gives you a writable IO-like object which streams what you write into
|
|
153
|
+
it to a GCS object using the resumable upload protocol over a signed URL. The size
|
|
154
|
+
of the upload does not need to be known beforehand.
|
|
155
|
+
email:
|
|
156
|
+
- me@julik.nl
|
|
157
|
+
executables: []
|
|
158
|
+
extensions: []
|
|
159
|
+
extra_rdoc_files: []
|
|
160
|
+
files:
|
|
161
|
+
- ".gitignore"
|
|
162
|
+
- ".standard.yml"
|
|
163
|
+
- ".yardopts"
|
|
164
|
+
- CHANGELOG.md
|
|
165
|
+
- Gemfile
|
|
166
|
+
- LICENSE
|
|
167
|
+
- README.md
|
|
168
|
+
- Rakefile
|
|
169
|
+
- gcs_put.gemspec
|
|
170
|
+
- lib/gcs_put.rb
|
|
171
|
+
- lib/gcs_put/byte_chunker.rb
|
|
172
|
+
- lib/gcs_put/resumable_upload.rb
|
|
173
|
+
- lib/gcs_put/signer.rb
|
|
174
|
+
- lib/gcs_put/transport.rb
|
|
175
|
+
- lib/gcs_put/transport/faraday.rb
|
|
176
|
+
- lib/gcs_put/transport/net_http.rb
|
|
177
|
+
- lib/gcs_put/version.rb
|
|
178
|
+
- rbi/gcs_put.rbi
|
|
179
|
+
- sig/gcs_put.rbs
|
|
180
|
+
homepage: https://github.com/julik/gcs_put
|
|
181
|
+
licenses:
|
|
182
|
+
- MIT
|
|
183
|
+
metadata:
|
|
184
|
+
homepage_uri: https://github.com/julik/gcs_put
|
|
185
|
+
source_code_uri: https://github.com/julik/gcs_put
|
|
186
|
+
changelog_uri: https://github.com/julik/gcs_put/blob/main/CHANGELOG.md
|
|
187
|
+
allowed_push_host: https://rubygems.org
|
|
188
|
+
rdoc_options: []
|
|
189
|
+
require_paths:
|
|
190
|
+
- lib
|
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
|
+
requirements:
|
|
193
|
+
- - ">="
|
|
194
|
+
- !ruby/object:Gem::Version
|
|
195
|
+
version: 3.2.0
|
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - ">="
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: '0'
|
|
201
|
+
requirements: []
|
|
202
|
+
rubygems_version: 3.6.2
|
|
203
|
+
specification_version: 4
|
|
204
|
+
summary: Streaming resumable uploads to Google Cloud Storage without knowing the size
|
|
205
|
+
in advance.
|
|
206
|
+
test_files: []
|