transloadit 3.1.2 → 4.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 +4 -4
- data/.gitattributes +44 -5
- data/.github/workflows/ci.yml +18 -15
- data/.gitignore +1 -1
- data/.standard.yml +2 -0
- data/CHANGELOG.md +17 -0
- data/Dockerfile +1 -1
- data/Gemfile +4 -0
- data/MIGRATING_TO_V4.md +136 -0
- data/README.md +55 -3
- data/lib/transloadit/assembly.rb +73 -14
- data/lib/transloadit/exception.rb +14 -6
- data/lib/transloadit/request.rb +54 -22
- data/lib/transloadit/response.rb +36 -34
- data/lib/transloadit/version.rb +1 -1
- data/lib/transloadit.rb +5 -1
- data/test/test_helper.rb +26 -0
- data/test/unit/test_transloadit.rb +1 -1
- data/test/unit/transloadit/test_assembly.rb +92 -9
- data/test/unit/transloadit/test_assembly_uploads.rb +272 -0
- data/test/unit/transloadit/test_request.rb +1 -19
- data/test/unit/transloadit/test_response.rb +33 -31
- data/test/unit/transloadit/test_transport_compatibility.rb +222 -0
- data/transloadit.gemspec +5 -4
- metadata +34 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c1220e478a424b85d5ac449fd7facf3790af3091a69435699a49480853b86fe
|
|
4
|
+
data.tar.gz: 544d23e833e4ee095744d91f948ca7bfc7616bb70e5d576042d44b3032510307
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 69178ed10b8fc21cd0c342e32aeecad79d886721e2c08b7df69e85a3d965faacef50054973a5b6dca7c72b885166a5f371416c641685df6679481d2e310dd0c8
|
|
7
|
+
data.tar.gz: 4a2393daa75a76414b208891d63a2d2d0a88e2c50e22da4b634a33fe7696cb084348fb26b1e823c5b45ca6feda168b12e8450a7dfa290a40d7e7bd29ea082d19
|
data/.gitattributes
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*.
|
|
1
|
+
/.yarn/** linguist-vendored
|
|
2
|
+
/.yarn/releases/* binary
|
|
3
|
+
/.yarn/plugins/**/* binary
|
|
4
|
+
/.pnp.* binary linguist-generated
|
|
5
|
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
|
6
|
+
*.aac filter=lfs diff=lfs merge=lfs -text
|
|
7
|
+
*.ai filter=lfs diff=lfs merge=lfs -text
|
|
8
|
+
*.avi filter=lfs diff=lfs merge=lfs -text
|
|
9
|
+
*.bmp filter=lfs diff=lfs merge=lfs -text
|
|
10
|
+
*.br filter=lfs diff=lfs merge=lfs -text
|
|
11
|
+
*.cr2 filter=lfs diff=lfs merge=lfs -text
|
|
12
|
+
*.dll filter=lfs diff=lfs merge=lfs -text
|
|
13
|
+
*.doc filter=lfs diff=lfs merge=lfs -text
|
|
14
|
+
*.docx filter=lfs diff=lfs merge=lfs -text
|
|
15
|
+
*.exe filter=lfs diff=lfs merge=lfs -text
|
|
16
|
+
*.flac filter=lfs diff=lfs merge=lfs -text
|
|
17
|
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
|
18
|
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
|
19
|
+
*.ico filter=lfs diff=lfs merge=lfs -text
|
|
20
|
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
|
21
|
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
|
22
|
+
*.JPG filter=lfs diff=lfs merge=lfs -text
|
|
23
|
+
*.m4a filter=lfs diff=lfs merge=lfs -text
|
|
24
|
+
*.m4v filter=lfs diff=lfs merge=lfs -text
|
|
25
|
+
*.mkv filter=lfs diff=lfs merge=lfs -text
|
|
26
|
+
*.mov filter=lfs diff=lfs merge=lfs -text
|
|
27
|
+
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
|
28
|
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
29
|
+
*.ogg filter=lfs diff=lfs merge=lfs -text
|
|
30
|
+
*.pages filter=lfs diff=lfs merge=lfs -text
|
|
31
|
+
*.pdf filter=lfs diff=lfs merge=lfs -text
|
|
32
|
+
*.png filter=lfs diff=lfs merge=lfs -text
|
|
33
|
+
*.PNG filter=lfs diff=lfs merge=lfs -text
|
|
34
|
+
*.psd filter=lfs diff=lfs merge=lfs -text
|
|
35
|
+
*.pxm filter=lfs diff=lfs merge=lfs -text
|
|
36
|
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
|
37
|
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
|
38
|
+
*.tif filter=lfs diff=lfs merge=lfs -text
|
|
39
|
+
*.tiff filter=lfs diff=lfs merge=lfs -text
|
|
40
|
+
*.webm filter=lfs diff=lfs merge=lfs -text
|
|
41
|
+
*.webp filter=lfs diff=lfs merge=lfs -text
|
|
42
|
+
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
|
43
|
+
*.xlsx filter=lfs diff=lfs merge=lfs -text
|
|
44
|
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -13,32 +13,33 @@ jobs:
|
|
|
13
13
|
strategy:
|
|
14
14
|
matrix:
|
|
15
15
|
ruby:
|
|
16
|
-
- 3.
|
|
17
|
-
- 3.
|
|
18
|
-
- 3.
|
|
19
|
-
- 3.
|
|
20
|
-
-
|
|
21
|
-
- truffleruby
|
|
16
|
+
- "3.1"
|
|
17
|
+
- "3.2"
|
|
18
|
+
- "3.3"
|
|
19
|
+
- "3.4"
|
|
20
|
+
- "4.0"
|
|
22
21
|
fail-fast: false
|
|
23
22
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
lfs: true
|
|
25
26
|
- uses: actions/setup-node@v4
|
|
26
27
|
with:
|
|
27
28
|
node-version: 20
|
|
28
29
|
- run: npm install -g tsx
|
|
29
|
-
- uses: ruby/setup-ruby@
|
|
30
|
+
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f
|
|
30
31
|
with:
|
|
31
32
|
ruby-version: ${{ matrix.ruby }}
|
|
32
33
|
bundler-cache: true
|
|
33
34
|
- run: bundle exec standardrb
|
|
34
35
|
- name: Run tests
|
|
35
36
|
env:
|
|
36
|
-
COVERAGE: ${{ matrix.ruby == '
|
|
37
|
-
TEST_NODE_PARITY: ${{ matrix.ruby == '
|
|
37
|
+
COVERAGE: ${{ matrix.ruby == '4.0' && '1' || '0' }}
|
|
38
|
+
TEST_NODE_PARITY: ${{ matrix.ruby == '4.0' && '1' || '0' }}
|
|
38
39
|
run: bundle exec rake test
|
|
39
40
|
- name: Upload coverage to Codecov
|
|
40
|
-
if: matrix.ruby == '
|
|
41
|
-
uses: codecov/codecov-action@
|
|
41
|
+
if: matrix.ruby == '4.0'
|
|
42
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
|
|
42
43
|
with:
|
|
43
44
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
44
45
|
files: ./coverage/coverage.json
|
|
@@ -53,10 +54,12 @@ jobs:
|
|
|
53
54
|
TRANSLOADIT_KEY: ${{ secrets.TRANSLOADIT_KEY }}
|
|
54
55
|
TRANSLOADIT_SECRET: ${{ secrets.TRANSLOADIT_SECRET }}
|
|
55
56
|
steps:
|
|
56
|
-
- uses: actions/checkout@
|
|
57
|
-
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
with:
|
|
59
|
+
lfs: true
|
|
60
|
+
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f
|
|
58
61
|
with:
|
|
59
|
-
ruby-version:
|
|
62
|
+
ruby-version: "4.0"
|
|
60
63
|
bundler-cache: true
|
|
61
64
|
- name: Ensure e2e credentials are configured
|
|
62
65
|
if: ${{ env.TRANSLOADIT_KEY == '' || env.TRANSLOADIT_SECRET == '' }}
|
data/.gitignore
CHANGED
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
### Unreleased
|
|
2
|
+
|
|
3
|
+
### 4.1.0 / 2026-10-18
|
|
4
|
+
|
|
5
|
+
- Add `uploads: {name => io}` to Assembly options and `create!` for named multipart uploads, separate from signed JSON Instructions.
|
|
6
|
+
- Send custom `fields` only in signed JSON, preserving numbers, booleans, arrays, objects, and null without duplicate form fields.
|
|
7
|
+
- Keep IOs in legacy `fields` working as named uploads throughout v4, omit them from JSON, and warn to migrate to `uploads` before the next major release. Positional `file_N` uploads and `create!(fields: metadata)` overrides remain supported.
|
|
8
|
+
- Restore in-memory uploads to their original position on rate-limit retries and raise a clear `IOError` when a non-seekable upload cannot be retried.
|
|
9
|
+
|
|
10
|
+
### 4.0.0 / 2026-09-03
|
|
11
|
+
|
|
12
|
+
- BREAKING: Replace the long-dormant `rest-client` transport with Faraday 2 and `faraday-multipart`.
|
|
13
|
+
- BREAKING: Raise the minimum supported Ruby version from 3.0 to 3.1.
|
|
14
|
+
- Keep HTTP client types out of SDK responses and exceptions while preserving response status, headers, body access, multipart ordering, and rate-limit retries.
|
|
15
|
+
- Add a [v3 to v4 migration guide](MIGRATING_TO_V4.md).
|
|
16
|
+
- Test all Ruby versions from the supported minimum through Ruby 4.0.
|
|
17
|
+
|
|
1
18
|
### 3.1.2 / 2025-10-29
|
|
2
19
|
|
|
3
20
|
- Compress e2e fixture asset to keep gem size close to previous releases (kvz)
|
data/Dockerfile
CHANGED
data/Gemfile
CHANGED
|
@@ -5,4 +5,8 @@ gem "minitest"
|
|
|
5
5
|
gem "simplecov"
|
|
6
6
|
gem "simplecov-cobertura"
|
|
7
7
|
|
|
8
|
+
# Ruby 3.1 resolves MultiJson 1.19, which cannot parse JSON 3 (ruby-sdk#89).
|
|
9
|
+
# Keep its test bundle compatible without restricting consumers on newer Rubies.
|
|
10
|
+
gem "json", "< 3.0" if RUBY_VERSION.start_with?("3.1.")
|
|
11
|
+
|
|
8
12
|
gemspec
|
data/MIGRATING_TO_V4.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Migrating from v3 to v4
|
|
2
|
+
|
|
3
|
+
Version 4 replaces the `rest-client` HTTP transport with Faraday 2. Most applications only use the
|
|
4
|
+
Transloadit API exposed by this gem and do not need code changes. The migration is a major release
|
|
5
|
+
because v3 also delegated undocumented methods to RestClient response objects and exposed
|
|
6
|
+
RestClient exception inheritance.
|
|
7
|
+
|
|
8
|
+
Version 4 requires Ruby 3.1 or newer. The final v3 release allowed Ruby 3.0, so applications still
|
|
9
|
+
running Ruby 3.0 must upgrade Ruby before updating this gem.
|
|
10
|
+
|
|
11
|
+
## Update the dependency
|
|
12
|
+
|
|
13
|
+
Once v4 is released, update the application Gemfile and bundle:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "transloadit", "~> 4.0"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
bundle update transloadit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The SDK installs Faraday and its multipart middleware. Applications do not need to add Faraday
|
|
24
|
+
directly. Remove an explicit `rest-client` dependency only if the application does not use it for
|
|
25
|
+
anything else. Version 4 no longer installs or loads the `RestClient` constant on the application's
|
|
26
|
+
behalf.
|
|
27
|
+
|
|
28
|
+
## Responses
|
|
29
|
+
|
|
30
|
+
Use the SDK-owned response API:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
response = transloadit.assembly.get(assembly_id)
|
|
34
|
+
|
|
35
|
+
response["ok"]
|
|
36
|
+
response[:assembly_id]
|
|
37
|
+
response.body
|
|
38
|
+
response.headers
|
|
39
|
+
response.code
|
|
40
|
+
response.status
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`code` continues to return the integer HTTP status, and `status` is its new explicit alias. Header
|
|
44
|
+
names remain normalized to lowercase symbols with underscores, for example
|
|
45
|
+
`response.headers[:retry_after]`.
|
|
46
|
+
|
|
47
|
+
HTTP error statuses still return `Transloadit::Response` objects. They are not converted into
|
|
48
|
+
transport exceptions:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
response = transloadit.assembly.get(assembly_id)
|
|
52
|
+
|
|
53
|
+
if response.code >= 400
|
|
54
|
+
warn response["error"]
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Remove raw RestClient response usage
|
|
59
|
+
|
|
60
|
+
In v3, `Transloadit::Response` delegated unknown methods to a `RestClient::Response`. Version 4 no
|
|
61
|
+
longer exposes the underlying HTTP client. Replace calls to RestClient-specific response methods
|
|
62
|
+
with the SDK-owned response API above.
|
|
63
|
+
|
|
64
|
+
For example:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# v3: relied on the delegated RestClient object
|
|
68
|
+
response.raw_headers
|
|
69
|
+
|
|
70
|
+
# v4
|
|
71
|
+
response.headers
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If an application relies on another delegated method that has no equivalent, open an issue with the
|
|
75
|
+
use case before upgrading.
|
|
76
|
+
|
|
77
|
+
## Request failures
|
|
78
|
+
|
|
79
|
+
Network, TLS, and timeout failures now raise `Transloadit::Exception::RequestFailed` instead of a
|
|
80
|
+
RestClient exception:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# v3
|
|
84
|
+
begin
|
|
85
|
+
transloadit.assembly.get(assembly_id)
|
|
86
|
+
rescue RestClient::Exception => error
|
|
87
|
+
warn error.message
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# v4
|
|
91
|
+
begin
|
|
92
|
+
transloadit.assembly.get(assembly_id)
|
|
93
|
+
rescue Transloadit::Exception::RequestFailed => error
|
|
94
|
+
warn error.message
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The original adapter exception is retained as `error.cause` for diagnostics. Avoid branching on its
|
|
99
|
+
class so application behavior does not become coupled to Faraday.
|
|
100
|
+
|
|
101
|
+
## Rate-limit failures
|
|
102
|
+
|
|
103
|
+
Continue rescuing the SDK exception directly:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
begin
|
|
107
|
+
assembly.create!(file)
|
|
108
|
+
rescue Transloadit::Exception::RateLimitReached => error
|
|
109
|
+
retry_after = error.response.wait_time
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`RateLimitReached` remains a `StandardError` and retains its Transloadit response through
|
|
114
|
+
`error.response`, but it no longer inherits from `RestClient::RequestEntityTooLarge`. Replace code
|
|
115
|
+
that rescues the RestClient superclass with the SDK exception.
|
|
116
|
+
|
|
117
|
+
## Uploaded file handles
|
|
118
|
+
|
|
119
|
+
Version 4 does not close path-backed file objects supplied by the caller. Prefer a block so ownership
|
|
120
|
+
is explicit:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
File.open("video.mp4", "rb") do |file|
|
|
124
|
+
assembly.create!(file)
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Multipart field order is unchanged: `params` and `signature` are sent before file bodies.
|
|
129
|
+
|
|
130
|
+
## Upgrade checklist
|
|
131
|
+
|
|
132
|
+
- Update the gem and run the application's request and upload tests.
|
|
133
|
+
- Replace rescues of `RestClient::Exception` and `RestClient::RequestEntityTooLarge`.
|
|
134
|
+
- Replace calls to delegated RestClient response methods.
|
|
135
|
+
- Confirm the application closes files that it opens.
|
|
136
|
+
- Remove the direct `rest-client` dependency if nothing else uses it.
|
data/README.md
CHANGED
|
@@ -19,6 +19,9 @@ _If you run Ruby on Rails and are looking to integrate with the browser for file
|
|
|
19
19
|
gem install transloadit
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Upgrading from v3? See the [v4 migration guide](MIGRATING_TO_V4.md) for response, exception, and
|
|
23
|
+
file-handle compatibility notes.
|
|
24
|
+
|
|
22
25
|
## Usage
|
|
23
26
|
|
|
24
27
|
To get started, you need to require the 'transloadit' gem:
|
|
@@ -168,6 +171,38 @@ files = [open('puppies.jpg'), open('kittens.jpg'), open('ferrets.jpg')]
|
|
|
168
171
|
response = assembly.create! *files
|
|
169
172
|
```
|
|
170
173
|
|
|
174
|
+
For names that match a Step's `use.fields` selector, pass `uploads: {name => io}`
|
|
175
|
+
to `assembly` or `create!`. Uploads are multipart parts and are excluded from
|
|
176
|
+
the signed JSON Instructions. Open files in binary mode and close them after
|
|
177
|
+
the request; the SDK leaves caller-owned files open.
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
File.open('puppies.jpg', 'rb') do |photo|
|
|
181
|
+
response = transloadit.assembly(
|
|
182
|
+
steps: {
|
|
183
|
+
resize: {
|
|
184
|
+
robot: '/image/resize',
|
|
185
|
+
use: {fields: ['photo']},
|
|
186
|
+
width: 320,
|
|
187
|
+
height: 240
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
uploads: {photo: photo},
|
|
191
|
+
fields: {count: 2, enabled: false, details: {caption: 'Puppies'}}
|
|
192
|
+
).create!
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Alternatively, use `assembly.create!(uploads: {photo: photo}, fields: metadata)`.
|
|
197
|
+
Per-call `uploads` and `fields` each replace the corresponding Assembly option;
|
|
198
|
+
`uploads: {}` clears only the `uploads` option. Legacy IOs configured in `fields`
|
|
199
|
+
remain uploads for compatibility. Positional files still use `file_0`,
|
|
200
|
+
`file_1`, and so on, and take precedence over named uploads with those names.
|
|
201
|
+
Upload values must respond to `read`; the names `params` and `signature` are reserved.
|
|
202
|
+
For rate-limit retries, in-memory uploads must also support `pos` and `seek`;
|
|
203
|
+
the SDK restores their original position. A non-seekable upload raises `IOError`
|
|
204
|
+
if a retry is needed, instead of sending consumed bytes again.
|
|
205
|
+
|
|
171
206
|
### 3. Parallel Assembly
|
|
172
207
|
|
|
173
208
|
Transloadit allows you to perform several processing steps in parallel. You
|
|
@@ -223,9 +258,10 @@ has some nice examples for that.
|
|
|
223
258
|
|
|
224
259
|
### 5. Using fields
|
|
225
260
|
|
|
226
|
-
Transloadit allows you to submit
|
|
227
|
-
notification.
|
|
228
|
-
|
|
261
|
+
Transloadit allows you to submit custom metadata that you'll get back in the
|
|
262
|
+
notification. `fields` is included once in the signed JSON Instructions and is
|
|
263
|
+
never duplicated as form fields. Numbers, booleans, arrays, objects, and null
|
|
264
|
+
retain their JSON types. You can use fields like the following:
|
|
229
265
|
|
|
230
266
|
```ruby
|
|
231
267
|
require 'transloadit'
|
|
@@ -243,6 +279,17 @@ transloadit.assembly(
|
|
|
243
279
|
).create! open('/PATH/TO/FILE.mpg')
|
|
244
280
|
```
|
|
245
281
|
|
|
282
|
+
`create!(fields: metadata)` also sets custom fields and replaces any metadata
|
|
283
|
+
configured on the Assembly. Uploads and metadata may use the same name.
|
|
284
|
+
|
|
285
|
+
For compatibility, IO values passed directly in a `fields` hash still upload
|
|
286
|
+
under their field names throughout v4, but emit a deprecation warning. They are
|
|
287
|
+
excluded from JSON; other values in that hash remain custom metadata. Migrate
|
|
288
|
+
from `assembly(fields: {photo: file, count: 2})` to
|
|
289
|
+
`assembly(uploads: {photo: file}, fields: {count: 2})` before the next major release.
|
|
290
|
+
Explicit `uploads` take precedence over legacy IOs with the same name. Existing
|
|
291
|
+
`assembly(fields: {photo: file}).create!(fields: metadata)` calls continue working.
|
|
292
|
+
|
|
246
293
|
### 6. Notify URL
|
|
247
294
|
|
|
248
295
|
If you want to be notified when the processing is finished you can provide
|
|
@@ -461,6 +508,11 @@ for the latest [git main](https://rubydoc.info/github/transloadit/ruby-sdk/main/
|
|
|
461
508
|
Please see [ci.yml](https://github.com/transloadit/ruby-sdk/tree/main/.github/workflows/ci.yml) for a list of supported ruby versions. It may also work on older Rubies, but support for those is not guaranteed. If it doesn't work on one of the officially supported Rubies, please file a
|
|
462
509
|
[bug report](https://github.com/transloadit/ruby-sdk/issues). Compatibility patches for other Rubies are welcome.
|
|
463
510
|
|
|
511
|
+
On Ruby 3.1, add `gem "json", "< 3.0"` to your application's Gemfile while
|
|
512
|
+
[the older MultiJson/JSON compatibility issue](https://github.com/transloadit/ruby-sdk/issues/89)
|
|
513
|
+
is open. Ruby 3.2 and newer can use JSON 3 with
|
|
514
|
+
[MultiJson 1.21.2 or newer](https://github.com/sferik/multi_json/releases/tag/v1.21.2).
|
|
515
|
+
|
|
464
516
|
### Ruby 2.x
|
|
465
517
|
|
|
466
518
|
If you still need support for Ruby 2.x, 2.0.1 is the last version that supports it.
|
data/lib/transloadit/assembly.rb
CHANGED
|
@@ -44,25 +44,45 @@ class Transloadit::Assembly < Transloadit::ApiModel
|
|
|
44
44
|
# @option params [String] :template_id the ID of a
|
|
45
45
|
# {template}[https://transloadit.com/templates] to use instead of
|
|
46
46
|
# specifying params here directly
|
|
47
|
+
# @option params [Hash] :fields custom metadata, retained as typed JSON
|
|
48
|
+
# @option params [Hash<String, IO>] :uploads named multipart uploads;
|
|
49
|
+
# overrides the Assembly's uploads when supplied (use {} to clear them)
|
|
47
50
|
#
|
|
48
51
|
def create!(*ios, **params)
|
|
49
52
|
params[:steps] = _wrap_steps_in_hash(params[:steps]) unless params[:steps].nil?
|
|
50
53
|
|
|
54
|
+
legacy_uploads, = _split_fields(options[:fields])
|
|
55
|
+
if params.key?(:fields)
|
|
56
|
+
uploads, params[:fields] = _split_fields(params[:fields])
|
|
57
|
+
legacy_uploads.merge!(uploads)
|
|
58
|
+
end
|
|
59
|
+
unless legacy_uploads.empty?
|
|
60
|
+
warn "Passing upload IOs in fields is deprecated; use uploads: {name => io} instead."
|
|
61
|
+
end
|
|
62
|
+
|
|
51
63
|
extra_params = {}
|
|
52
|
-
|
|
64
|
+
[legacy_uploads, params.delete(:uploads) { options[:uploads] }].compact.each do |uploads|
|
|
65
|
+
uploads.each do |name, io|
|
|
66
|
+
name = name.to_s
|
|
67
|
+
raise ArgumentError, "Upload name #{name.inspect} is reserved" if %w[params signature].include?(name)
|
|
68
|
+
raise ArgumentError, "Upload #{name.inspect} must be an IO responding to read" unless io.respond_to?(:read)
|
|
69
|
+
|
|
70
|
+
extra_params[name] = io
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
# Keep positional file_N uploads and their precedence for existing callers.
|
|
74
|
+
ios.each_with_index { |io, i| extra_params["file_#{i}"] = io }
|
|
75
|
+
upload_positions = _upload_positions(extra_params.values)
|
|
53
76
|
|
|
54
77
|
trials = options[:tries] || DEFAULT_TRIES
|
|
55
78
|
(1..trials).each do |trial|
|
|
56
|
-
# update the payload with file entries
|
|
57
|
-
ios.each_with_index { |f, i| extra_params.update "file_#{i}": f }
|
|
58
|
-
|
|
59
79
|
response = _do_request(
|
|
60
80
|
"/assemblies", params, "post", extra_params
|
|
61
81
|
).extend!(Transloadit::Response::Assembly)
|
|
62
82
|
|
|
63
83
|
return response unless response.rate_limit?
|
|
64
84
|
|
|
65
|
-
_handle_rate_limit!(response,
|
|
85
|
+
_handle_rate_limit!(response, upload_positions, trial < trials)
|
|
66
86
|
end
|
|
67
87
|
end
|
|
68
88
|
|
|
@@ -123,14 +143,37 @@ class Transloadit::Assembly < Transloadit::ApiModel
|
|
|
123
143
|
# @return [Hash] a Transloadit-compatible Hash of the Assembly's contents
|
|
124
144
|
#
|
|
125
145
|
def to_hash
|
|
126
|
-
options.merge(
|
|
146
|
+
result = options.merge(
|
|
127
147
|
auth: transloadit.to_hash,
|
|
128
148
|
steps: steps
|
|
129
|
-
)
|
|
149
|
+
)
|
|
150
|
+
result.delete(:uploads)
|
|
151
|
+
_, result[:fields] = _split_fields(result[:fields]) if result[:fields]
|
|
152
|
+
result.delete_if { |_, value| value.nil? }
|
|
130
153
|
end
|
|
131
154
|
|
|
132
155
|
private
|
|
133
156
|
|
|
157
|
+
# Legacy fields may contain both named IOs and metadata. Only metadata belongs
|
|
158
|
+
# in the signed JSON; the IOs remain supported as uploads throughout v4.
|
|
159
|
+
def _split_fields(fields)
|
|
160
|
+
return [{}, nil] if fields.nil?
|
|
161
|
+
|
|
162
|
+
fields.partition { |_, value| value.respond_to?(:read) }.map(&:to_h)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def _upload_positions(uploads)
|
|
166
|
+
uploads.filter_map do |io|
|
|
167
|
+
# The transport reopens path-backed uploads for each attempt.
|
|
168
|
+
next if io.respond_to?(:path) && io.path
|
|
169
|
+
|
|
170
|
+
position = io.pos if io.respond_to?(:pos) && io.respond_to?(:seek)
|
|
171
|
+
[io, position]
|
|
172
|
+
rescue IOError, SystemCallError
|
|
173
|
+
[io, nil]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
134
177
|
#
|
|
135
178
|
# Returns a Transloadit-compatible Hash wrapping the +steps+ passed to it.
|
|
136
179
|
# Accepts any supported format the +steps+ could come in.
|
|
@@ -143,27 +186,43 @@ class Transloadit::Assembly < Transloadit::ApiModel
|
|
|
143
186
|
when nil then steps
|
|
144
187
|
when Hash then steps
|
|
145
188
|
when Transloadit::Step then steps.to_hash
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
when Array
|
|
190
|
+
names = steps.inject([]) do |array, step|
|
|
191
|
+
case step
|
|
192
|
+
when Transloadit::Step then array << step.name
|
|
193
|
+
when Hash then array + step.keys.map(&:to_s)
|
|
194
|
+
else
|
|
195
|
+
raise ArgumentError, "Unsupported array element"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if names.uniq != names
|
|
148
200
|
raise ArgumentError, "There are different Assembly steps using the same name"
|
|
149
201
|
end
|
|
150
202
|
steps.inject({}) { |h, s| h.update s }
|
|
203
|
+
else
|
|
204
|
+
raise ArgumentError, "Unsupported steps class"
|
|
151
205
|
end
|
|
152
206
|
end
|
|
153
207
|
|
|
154
208
|
#
|
|
155
|
-
#
|
|
209
|
+
# Restores in-memory upload positions and waits before retrying.
|
|
156
210
|
# Should be called when assembly rate limit is reached.
|
|
157
211
|
#
|
|
158
212
|
# @param [Response] response assembly response that comes with a rate limit
|
|
159
|
-
# @param [Array<
|
|
213
|
+
# @param [Array<Array>] upload_positions IOs and their positions before upload
|
|
160
214
|
#
|
|
161
|
-
def _handle_rate_limit!(response,
|
|
215
|
+
def _handle_rate_limit!(response, upload_positions, is_retrying)
|
|
162
216
|
if is_retrying
|
|
217
|
+
upload_positions.each do |io, position|
|
|
218
|
+
raise IOError if position.nil?
|
|
219
|
+
|
|
220
|
+
io.seek(position)
|
|
221
|
+
rescue IOError, SystemCallError
|
|
222
|
+
raise IOError, "Cannot retry a non-seekable upload; use a file or an IO supporting pos and seek"
|
|
223
|
+
end
|
|
163
224
|
warn "Rate limit reached. Waiting for #{response.wait_time} seconds before retrying."
|
|
164
225
|
sleep response.wait_time
|
|
165
|
-
# RestClient closes file streams at the end of a request.
|
|
166
|
-
ios.collect! { |file| File.open file.path }
|
|
167
226
|
else
|
|
168
227
|
raise Transloadit::Exception::RateLimitReached.new(response)
|
|
169
228
|
end
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
require "transloadit"
|
|
2
2
|
|
|
3
|
-
require "rest-client"
|
|
4
|
-
|
|
5
3
|
module Transloadit::Exception
|
|
4
|
+
#
|
|
5
|
+
# Exception raised when an HTTP request cannot be completed.
|
|
6
|
+
# The original transport error is available through +cause+.
|
|
7
|
+
#
|
|
8
|
+
class RequestFailed < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
6
11
|
#
|
|
7
12
|
# Exception raised when Rate limit error response is returned from the API.
|
|
8
13
|
# See {Rate Limiting}[https://transloadit.com/docs/api-docs/#rate-limiting]
|
|
9
14
|
#
|
|
10
|
-
class RateLimitReached <
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
class RateLimitReached < StandardError
|
|
16
|
+
# @return [Transloadit::Response] the API response that reported the rate limit
|
|
17
|
+
attr_reader :response
|
|
18
|
+
|
|
19
|
+
def initialize(response)
|
|
20
|
+
@response = response
|
|
21
|
+
super("Transloadit Rate Limit Reached. Retry in #{response.wait_time} seconds")
|
|
14
22
|
end
|
|
15
23
|
end
|
|
16
24
|
|