fal 0.0.1 → 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 +4 -4
- data/.env.example +1 -0
- data/.rubocop.yml +8 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +90 -26
- data/README.md +272 -0
- data/lib/fal/client.rb +151 -0
- data/lib/fal/model.rb +166 -0
- data/lib/fal/price.rb +92 -0
- data/lib/fal/price_estimate.rb +89 -0
- data/lib/fal/request.rb +161 -0
- data/lib/fal/stream.rb +92 -0
- data/lib/fal/version.rb +1 -1
- data/lib/fal/webhook_request.rb +99 -0
- data/lib/fal.rb +102 -0
- data/rbi/fal/client.rbi +59 -0
- data/rbi/fal/fal.rbi +68 -0
- data/rbi/fal/model.rbi +130 -0
- data/rbi/fal/price.rbi +49 -0
- data/rbi/fal/price_estimate.rbi +61 -0
- data/rbi/fal/request.rbi +88 -0
- data/rbi/fal/stream.rbi +37 -0
- data/rbi/fal/version.rbi +6 -0
- data/rbi/fal/webhook_request.rbi +61 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/.gitignore +2 -0
- metadata +21 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ff4bd4724ee6b7ea39f7c72f51503e4e918b50f019972a08836efd6d22fdffc
|
|
4
|
+
data.tar.gz: 4f9020293f0b639487e836668116fadfcd51768ef0401e335a18394be155c930
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7646551a9cffa3a58b9ce0ac08f48e258cedd9e19f7ad18e9ae8fc3fe0aa85c774e5a21a283026ef5a22e0126c8c3db52d2667bc7cca285f8d9b6d74c4ac3b7
|
|
7
|
+
data.tar.gz: 0f1f6137e731796d859ff4bd51eb92078453530498dfd72d436e8c9999dd4f1f26087a99c010f533fb231bb2012186d10fee972811ff44d268e77a71d1180e3a
|
data/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAL_KEY=
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,62 +1,126 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
fal (0.0
|
|
4
|
+
fal (0.1.0)
|
|
5
5
|
faraday (>= 1)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
|
|
10
|
+
addressable (2.8.7)
|
|
11
|
+
public_suffix (>= 2.0.2, < 7.0)
|
|
12
|
+
ast (2.4.3)
|
|
13
|
+
base64 (0.3.0)
|
|
14
|
+
benchmark (0.5.0)
|
|
15
|
+
bigdecimal (3.3.1)
|
|
16
|
+
crack (1.0.1)
|
|
17
|
+
bigdecimal
|
|
18
|
+
rexml
|
|
19
|
+
dotenv (3.1.8)
|
|
20
|
+
erubi (1.13.1)
|
|
11
21
|
faraday (2.14.0)
|
|
12
22
|
faraday-net_http (>= 2.0, < 3.5)
|
|
13
23
|
json
|
|
14
24
|
logger
|
|
15
25
|
faraday-net_http (3.4.1)
|
|
16
26
|
net-http (>= 0.5.0)
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
hashdiff (1.2.1)
|
|
28
|
+
json (2.15.2)
|
|
29
|
+
language_server-protocol (3.17.0.5)
|
|
30
|
+
lint_roller (1.1.0)
|
|
19
31
|
logger (1.7.0)
|
|
20
|
-
minitest (5.
|
|
21
|
-
net-http (0.
|
|
32
|
+
minitest (5.26.0)
|
|
33
|
+
net-http (0.7.0)
|
|
22
34
|
uri
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
netrc (0.11.0)
|
|
36
|
+
parallel (1.27.0)
|
|
37
|
+
parser (3.3.10.0)
|
|
25
38
|
ast (~> 2.4.1)
|
|
26
39
|
racc
|
|
27
|
-
|
|
40
|
+
prism (1.6.0)
|
|
41
|
+
public_suffix (6.0.2)
|
|
42
|
+
racc (1.8.1)
|
|
28
43
|
rainbow (3.1.1)
|
|
29
|
-
rake (13.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
rake (13.3.1)
|
|
45
|
+
rbi (0.3.7)
|
|
46
|
+
prism (~> 1.0)
|
|
47
|
+
rbs (>= 3.4.4)
|
|
48
|
+
rbs (3.9.5)
|
|
49
|
+
logger
|
|
50
|
+
regexp_parser (2.11.3)
|
|
51
|
+
rexml (3.4.4)
|
|
52
|
+
rubocop (1.81.7)
|
|
34
53
|
json (~> 2.3)
|
|
35
|
-
language_server-protocol (
|
|
54
|
+
language_server-protocol (~> 3.17.0.2)
|
|
55
|
+
lint_roller (~> 1.1.0)
|
|
36
56
|
parallel (~> 1.10)
|
|
37
57
|
parser (>= 3.3.0.2)
|
|
38
58
|
rainbow (>= 2.2.2, < 4.0)
|
|
39
|
-
regexp_parser (>=
|
|
40
|
-
|
|
41
|
-
rubocop-ast (>= 1.31.1, < 2.0)
|
|
59
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
60
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
42
61
|
ruby-progressbar (~> 1.7)
|
|
43
|
-
unicode-display_width (>= 2.4.0, <
|
|
44
|
-
rubocop-ast (1.
|
|
45
|
-
parser (>= 3.3.
|
|
62
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
63
|
+
rubocop-ast (1.47.1)
|
|
64
|
+
parser (>= 3.3.7.2)
|
|
65
|
+
prism (~> 1.4)
|
|
46
66
|
ruby-progressbar (1.13.0)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
sorbet (0.6.12689)
|
|
68
|
+
sorbet-static (= 0.6.12689)
|
|
69
|
+
sorbet-runtime (0.6.12689)
|
|
70
|
+
sorbet-static (0.6.12689-aarch64-linux)
|
|
71
|
+
sorbet-static (0.6.12689-universal-darwin)
|
|
72
|
+
sorbet-static (0.6.12689-x86_64-linux)
|
|
73
|
+
sorbet-static-and-runtime (0.6.12689)
|
|
74
|
+
sorbet (= 0.6.12689)
|
|
75
|
+
sorbet-runtime (= 0.6.12689)
|
|
76
|
+
spoom (1.6.3)
|
|
77
|
+
erubi (>= 1.10.0)
|
|
78
|
+
prism (>= 0.28.0)
|
|
79
|
+
rbi (>= 0.3.3)
|
|
80
|
+
rexml (>= 3.2.6)
|
|
81
|
+
sorbet-static-and-runtime (>= 0.5.10187)
|
|
82
|
+
thor (>= 0.19.2)
|
|
83
|
+
tapioca (0.16.11)
|
|
84
|
+
benchmark
|
|
85
|
+
bundler (>= 2.2.25)
|
|
86
|
+
netrc (>= 0.11.0)
|
|
87
|
+
parallel (>= 1.21.0)
|
|
88
|
+
rbi (~> 0.2)
|
|
89
|
+
sorbet-static-and-runtime (>= 0.5.11087)
|
|
90
|
+
spoom (>= 1.2.0)
|
|
91
|
+
thor (>= 1.2.0)
|
|
92
|
+
yard-sorbet
|
|
93
|
+
thor (1.4.0)
|
|
94
|
+
unicode-display_width (3.2.0)
|
|
95
|
+
unicode-emoji (~> 4.1)
|
|
96
|
+
unicode-emoji (4.1.0)
|
|
97
|
+
uri (1.1.0)
|
|
98
|
+
vcr (6.3.1)
|
|
99
|
+
base64
|
|
100
|
+
webmock (3.26.1)
|
|
101
|
+
addressable (>= 2.8.0)
|
|
102
|
+
crack (>= 0.3.2)
|
|
103
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
104
|
+
yard (0.9.37)
|
|
105
|
+
yard-sorbet (0.9.0)
|
|
106
|
+
sorbet-runtime
|
|
107
|
+
yard
|
|
50
108
|
|
|
51
109
|
PLATFORMS
|
|
52
|
-
|
|
53
|
-
|
|
110
|
+
aarch64-linux
|
|
111
|
+
universal-darwin
|
|
112
|
+
x86_64-linux
|
|
54
113
|
|
|
55
114
|
DEPENDENCIES
|
|
115
|
+
dotenv
|
|
56
116
|
fal!
|
|
57
117
|
minitest (~> 5.0)
|
|
58
118
|
rake (~> 13.0)
|
|
59
119
|
rubocop (~> 1.21)
|
|
120
|
+
sorbet
|
|
121
|
+
tapioca
|
|
122
|
+
vcr
|
|
123
|
+
webmock
|
|
60
124
|
|
|
61
125
|
BUNDLED WITH
|
|
62
126
|
2.5.7
|
data/README.md
CHANGED
|
@@ -9,3 +9,275 @@ Install the gem and add to the application"s Gemfile by executing:
|
|
|
9
9
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
10
10
|
|
|
11
11
|
$ gem install fal
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Configuration
|
|
16
|
+
|
|
17
|
+
Configure the client once at boot (e.g., in Rails an initializer) using `Fal.configure`.
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
Fal.configure do |config|
|
|
21
|
+
config.api_key = "your-key" # Optional. Defaults to ENV["FAL_KEY"] if not set.
|
|
22
|
+
config.queue_base = "https://queue.fal.run" # Optional (default: https://queue.fal.run)
|
|
23
|
+
config.sync_base = "https://fal.run" # Optional (default: https://fal.run)
|
|
24
|
+
config.api_base = "https://api.fal.ai/v1" # Optional (default: https://api.fal.ai/v1)
|
|
25
|
+
config.request_timeout = 120 # Optional (default: 120)
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Create a queued request
|
|
30
|
+
|
|
31
|
+
The Queue API is the recommended way to call models on fal. Provide a `endpoint_id` in "namespace/name" format and an input payload.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
endpoint_id = "fal-ai/fast-sdxl"
|
|
35
|
+
|
|
36
|
+
request = Fal::Request.create!(
|
|
37
|
+
endpoint_id: endpoint_id,
|
|
38
|
+
input: { prompt: "a cat" }
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
request.id # => request_id from fal
|
|
42
|
+
request.status # => "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can also specify a webhook URL to be notified when the request is finished.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
request = Fal::Request.create!(
|
|
49
|
+
endpoint_id: endpoint_id,
|
|
50
|
+
input: { prompt: "a cat playing piano" },
|
|
51
|
+
webhook_url: "https://example.com/fal/webhook"
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Get request status (find and reload)
|
|
56
|
+
|
|
57
|
+
Fetch the current status by id:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
status = Fal::Request.find_by!(id: request.id, endpoint_id: endpoint_id)
|
|
61
|
+
status.in_queue? # => true/false
|
|
62
|
+
status.in_progress? # => true/false
|
|
63
|
+
status.completed? # => true/false
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Reload an instance in-place, optionally including logs.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
request.reload! # refreshes state
|
|
70
|
+
request.reload!(logs: true)
|
|
71
|
+
request.logs # => array of log entries (if provided by model and logs=1)
|
|
72
|
+
# When status is COMPLETED, reload! will also fetch and set request.response
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Status constants are available for direct comparisons:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Fal::Request::Status::IN_QUEUE
|
|
79
|
+
Fal::Request::Status::IN_PROGRESS
|
|
80
|
+
Fal::Request::Status::COMPLETED
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Fetch the response payload after completion
|
|
84
|
+
|
|
85
|
+
Call `reload!` to populate `request.response`:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# poll until completed
|
|
89
|
+
until request.completed?
|
|
90
|
+
request.reload!
|
|
91
|
+
sleep 1
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
request.response # => model-specific response body
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Cancel a request
|
|
98
|
+
|
|
99
|
+
Requests that are still in the queue can be cancelled:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
request.cancel! # => { "status" => "CANCELLATION_REQUESTED" }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Webhooks
|
|
106
|
+
|
|
107
|
+
fal can POST a webhook to your server when a request completes. Use `Fal::WebhookRequest` to parse the incoming payload.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# rails controller example
|
|
111
|
+
class FalWebhooksController < ApplicationController
|
|
112
|
+
skip_before_action :verify_authenticity_token
|
|
113
|
+
|
|
114
|
+
def create
|
|
115
|
+
webhook = Fal::WebhookRequest.from_rack_request(request)
|
|
116
|
+
|
|
117
|
+
if webhook.success?
|
|
118
|
+
# webhook.response contains the model-specific payload
|
|
119
|
+
# webhook.logs, webhook.metrics may also be present
|
|
120
|
+
head :ok
|
|
121
|
+
else
|
|
122
|
+
Rails.logger.error("fal webhook error: #{webhook.error} detail=#{webhook.error_detail}")
|
|
123
|
+
head :ok
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Error handling
|
|
130
|
+
|
|
131
|
+
HTTP and API errors raise typed exceptions:
|
|
132
|
+
|
|
133
|
+
- `Fal::UnauthorizedError` (401)
|
|
134
|
+
- `Fal::ForbiddenError` (403)
|
|
135
|
+
- `Fal::NotFoundError` (404)
|
|
136
|
+
- `Fal::ServerError` (other non-success)
|
|
137
|
+
|
|
138
|
+
Rescue them as needed:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
begin
|
|
142
|
+
Fal::Request.create!(endpoint_id: endpoint_id, input: { prompt: "hi" })
|
|
143
|
+
rescue Fal::UnauthorizedError
|
|
144
|
+
# handle invalid/missing FAL_KEY
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Stream synchronous responses
|
|
149
|
+
|
|
150
|
+
Use `stream!` for SSE streaming from synchronous endpoints. It yields each chunk’s data Hash and returns a `Fal::Request` whose `response` contains the last chunk’s payload.
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
endpoint_id = "fal-ai/flux/dev"
|
|
154
|
+
|
|
155
|
+
last = Fal::Request.stream!(endpoint_id: endpoint_id, input: { prompt: "a cat" }) do |chunk|
|
|
156
|
+
# chunk is a Hash, e.g. { images: [...] }
|
|
157
|
+
puts chunk
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
last.completed? # => true/false
|
|
161
|
+
last.response # => last streamed data hash (e.g., { "response" => { ... } } or final payload)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Pricing
|
|
165
|
+
|
|
166
|
+
Use the Platform API to fetch per-endpoint pricing.
|
|
167
|
+
|
|
168
|
+
Find pricing for a single endpoint:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
price = Fal::Price.find_by(endpoint_id: "fal-ai/flux/dev")
|
|
172
|
+
price.unit_price # => e.g., 0.025
|
|
173
|
+
price.unit # => e.g., "image"
|
|
174
|
+
price.currency # => e.g., "USD"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Iterate through all prices (auto-paginates):
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
Fal::Price.each do |p|
|
|
181
|
+
puts "#{p.endpoint_id} => #{p.unit_price} #{p.currency} per #{p.unit}"
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Collect all prices as an array:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
prices = Fal::Price.all
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Estimate cost
|
|
192
|
+
|
|
193
|
+
Compute a total cost estimate across endpoints using historical API price or unit price.
|
|
194
|
+
|
|
195
|
+
Unit price (uses billing units like images/videos):
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
estimate = Fal::PriceEstimate.create(
|
|
199
|
+
estimate_type: Fal::PriceEstimate::EstimateType::UNIT_PRICE,
|
|
200
|
+
endpoints: [
|
|
201
|
+
# You can pass unit_quantity directly
|
|
202
|
+
Fal::PriceEstimate::Endpoint.new(endpoint_id: "fal-ai/flux/dev", unit_quantity: 50),
|
|
203
|
+
# Or use call_quantity as a convenience alias for units
|
|
204
|
+
Fal::PriceEstimate::Endpoint.new(endpoint_id: "fal-ai/flux-pro", call_quantity: 25)
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
estimate.estimate_type # => "unit_price"
|
|
209
|
+
estimate.total_cost # => e.g., 1.88
|
|
210
|
+
estimate.currency # => "USD"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Historical API price (uses calls per endpoint):
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
estimate = Fal::PriceEstimate.create(
|
|
217
|
+
estimate_type: Fal::PriceEstimate::EstimateType::HISTORICAL_API_PRICE,
|
|
218
|
+
endpoints: [
|
|
219
|
+
Fal::PriceEstimate::Endpoint.new(endpoint_id: "fal-ai/flux/dev", call_quantity: 100)
|
|
220
|
+
]
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Models
|
|
225
|
+
|
|
226
|
+
List, search, and find models via the Models API.
|
|
227
|
+
|
|
228
|
+
Find a model by endpoint ID:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
model = Fal::Model.find_by(endpoint_id: "fal-ai/flux/dev")
|
|
232
|
+
model.endpoint_id # => "fal-ai/flux/dev"
|
|
233
|
+
model.display_name # => e.g., "FLUX.1 [dev]"
|
|
234
|
+
model.category # => e.g., "text-to-image"
|
|
235
|
+
model.status # => "active" | "deprecated"
|
|
236
|
+
model.tags # => ["fast", "pro"]
|
|
237
|
+
model.model_url # => "https://fal.run/..."
|
|
238
|
+
model.thumbnail_url # => "https://..."
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Iterate or collect all models (auto-paginates):
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
Fal::Model.each do |m|
|
|
245
|
+
puts m.endpoint_id
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
all_models = Fal::Model.all
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Search with filters:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
results = Fal::Model.search(query: "text to image", status: "active")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Get a model’s price (memoized):
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
price = model.price
|
|
261
|
+
price.unit_price # => e.g., 0.025
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Run a request for a model (uses the model's `endpoint_id` as `endpoint_id`):
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
request = model.run(input: { prompt: "a cat" })
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Development
|
|
271
|
+
|
|
272
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
273
|
+
|
|
274
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to rubygems.org.
|
|
275
|
+
|
|
276
|
+
For local development, copy the example environment file and set your API key so `bin/console` can load it automatically:
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
cp .env.example .env
|
|
280
|
+
echo 'FAL_KEY=your_api_key_here' >> .env
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The console uses dotenv to load `.env`, so `Fal.configure` will default to `ENV["FAL_KEY"]`.
|
data/lib/fal/client.rb
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fal
|
|
4
|
+
class Client
|
|
5
|
+
attr_accessor :configuration
|
|
6
|
+
|
|
7
|
+
def initialize(configuration = Fal.configuration)
|
|
8
|
+
@configuration = configuration
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def post(path, payload = {}, headers: {})
|
|
12
|
+
response = connection.post(build_url(path)) do |request|
|
|
13
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
14
|
+
request.headers["Content-Type"] = "application/json"
|
|
15
|
+
request.headers["Accept"] = "application/json"
|
|
16
|
+
request.headers.merge!(headers)
|
|
17
|
+
request.body = payload.compact.to_json
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
handle_error(response) unless response.success?
|
|
21
|
+
|
|
22
|
+
parse_json(response.body)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Perform a POST against the platform API base (api.fal.ai/v1)
|
|
26
|
+
# @param path [String]
|
|
27
|
+
# @param payload [Hash]
|
|
28
|
+
# @param headers [Hash]
|
|
29
|
+
# @return [Hash, nil]
|
|
30
|
+
def post_api(path, payload = {}, headers: {})
|
|
31
|
+
response = connection.post(build_api_url(path)) do |request|
|
|
32
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
33
|
+
request.headers["Content-Type"] = "application/json"
|
|
34
|
+
request.headers["Accept"] = "application/json"
|
|
35
|
+
request.headers.merge!(headers)
|
|
36
|
+
request.body = payload.compact.to_json
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
handle_error(response) unless response.success?
|
|
40
|
+
|
|
41
|
+
parse_json(response.body)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Perform a POST to the streaming (sync) base with SSE/text-event-stream handling.
|
|
45
|
+
# The provided on_data Proc will be used to receive chunked data.
|
|
46
|
+
# @param path [String]
|
|
47
|
+
# @param payload [Hash]
|
|
48
|
+
# @param on_data [Proc] called with chunks as they arrive
|
|
49
|
+
# @return [void]
|
|
50
|
+
def post_stream(path, payload = {}, on_data:)
|
|
51
|
+
url = build_sync_url(path)
|
|
52
|
+
connection.post(url) do |request|
|
|
53
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
54
|
+
request.headers["Accept"] = "text/event-stream"
|
|
55
|
+
request.headers["Cache-Control"] = "no-store"
|
|
56
|
+
request.headers["Content-Type"] = "application/json"
|
|
57
|
+
request.body = payload.compact.to_json
|
|
58
|
+
request.options.on_data = on_data
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def get(path, query: nil, headers: {})
|
|
63
|
+
url = build_url(path)
|
|
64
|
+
url = "#{url}?#{URI.encode_www_form(query)}" if query && !query.empty?
|
|
65
|
+
|
|
66
|
+
response = connection.get(url) do |request|
|
|
67
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
68
|
+
request.headers["Accept"] = "application/json"
|
|
69
|
+
request.headers.merge!(headers)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
handle_error(response) unless response.success?
|
|
73
|
+
|
|
74
|
+
parse_json(response.body)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Perform a GET against the platform API base (api.fal.ai/v1)
|
|
78
|
+
# @param path [String]
|
|
79
|
+
# @param query [Hash, nil]
|
|
80
|
+
# @param headers [Hash]
|
|
81
|
+
# @return [Hash, nil]
|
|
82
|
+
def get_api(path, query: nil, headers: {})
|
|
83
|
+
url = build_api_url(path)
|
|
84
|
+
url = "#{url}?#{URI.encode_www_form(query)}" if query && !query.empty?
|
|
85
|
+
|
|
86
|
+
response = connection.get(url) do |request|
|
|
87
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
88
|
+
request.headers["Accept"] = "application/json"
|
|
89
|
+
request.headers.merge!(headers)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
handle_error(response) unless response.success?
|
|
93
|
+
|
|
94
|
+
parse_json(response.body)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def put(path)
|
|
98
|
+
response = connection.put(build_url(path)) do |request|
|
|
99
|
+
request.headers["Authorization"] = "Key #{@configuration.api_key}" if @configuration.api_key
|
|
100
|
+
request.headers["Content-Type"] = "application/json"
|
|
101
|
+
request.headers["Accept"] = "application/json"
|
|
102
|
+
request.body = {}.to_json
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
handle_error(response) unless response.success?
|
|
106
|
+
|
|
107
|
+
parse_json(response.body)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def handle_error(response)
|
|
111
|
+
case response.status
|
|
112
|
+
when 401
|
|
113
|
+
raise UnauthorizedError, response.body
|
|
114
|
+
when 403
|
|
115
|
+
raise ForbiddenError, response.body
|
|
116
|
+
when 404
|
|
117
|
+
raise NotFoundError, response.body
|
|
118
|
+
else
|
|
119
|
+
raise ServerError, response.body
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def parse_json(body)
|
|
126
|
+
return nil if body.nil? || body.strip.empty?
|
|
127
|
+
|
|
128
|
+
JSON.parse(body)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_url(path)
|
|
132
|
+
"#{@configuration.queue_base}#{path}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def build_sync_url(path)
|
|
136
|
+
"#{@configuration.sync_base}#{path}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_api_url(path)
|
|
140
|
+
"#{@configuration.api_base}#{path}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def connection
|
|
144
|
+
Faraday.new do |faraday|
|
|
145
|
+
faraday.request :url_encoded
|
|
146
|
+
faraday.options.timeout = @configuration.request_timeout
|
|
147
|
+
faraday.options.open_timeout = @configuration.request_timeout
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|