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.
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fal
4
+ # WebhookRequest parses incoming webhook payloads from fal and exposes
5
+ # convenient helpers to inspect success/error and access the response payload.
6
+ # Follows Rails-like naming with predicate helpers.
7
+ class WebhookRequest
8
+ # Webhook status values.
9
+ module Status
10
+ # @return [String]
11
+ OK = "OK"
12
+ # @return [String]
13
+ ERROR = "ERROR"
14
+ end
15
+
16
+ # @return [String, nil] The request identifier
17
+ attr_reader :request_id
18
+ # @return [String, nil] The gateway request identifier, when present
19
+ attr_reader :gateway_request_id
20
+ # @return [String, nil] Webhook status (OK/ERROR) when provided
21
+ attr_reader :status
22
+ # @return [String, nil] Error message when provided
23
+ attr_reader :error
24
+ # @return [Hash, nil] Model-specific response payload
25
+ attr_reader :response
26
+ # @return [Array<Hash>, nil] Log entries, when present
27
+ attr_reader :logs
28
+ # @return [Hash, nil] Metrics, when present
29
+ attr_reader :metrics
30
+ # @return [Hash] The raw parsed payload
31
+ attr_reader :raw
32
+
33
+ # Initialize from a parsed payload Hash (string keys expected, tolerant of symbol keys).
34
+ # @param attributes [Hash]
35
+ def initialize(attributes)
36
+ @raw = attributes
37
+ reset_attributes(attributes)
38
+ end
39
+
40
+ class << self
41
+ # Build from a JSON string body.
42
+ # @param json [String]
43
+ # @return [Fal::WebhookRequest]
44
+ def from_json(json)
45
+ new(JSON.parse(json))
46
+ end
47
+
48
+ # Build from a Rack::Request.
49
+ # @param request [#body]
50
+ # @return [Fal::WebhookRequest]
51
+ def from_rack_request(request)
52
+ body = request.body.read
53
+ request.body.rewind if request.body.respond_to?(:rewind)
54
+ from_json(body)
55
+ end
56
+
57
+ # Build from a Hash payload.
58
+ # @param payload [Hash]
59
+ # @return [Fal::WebhookRequest]
60
+ def from_hash(payload)
61
+ new(payload)
62
+ end
63
+ end
64
+
65
+ # @return [Boolean]
66
+ def success?
67
+ @status == Status::OK || (
68
+ @status.nil? && @error.nil?
69
+ )
70
+ end
71
+
72
+ # @return [Boolean]
73
+ def error?
74
+ !success?
75
+ end
76
+
77
+ # Back-compat alias matching older naming (payload vs response).
78
+ # @return [Hash, nil]
79
+ def payload
80
+ @response
81
+ end
82
+
83
+ # @return [String, nil] Any nested error detail
84
+ def error_detail = @response&.dig(:detail)
85
+
86
+ private
87
+
88
+ def reset_attributes(attributes)
89
+ attributes = Fal.deep_symbolize_keys(attributes)
90
+ @request_id = attributes[:request_id]
91
+ @gateway_request_id = attributes[:gateway_request_id]
92
+ @status = attributes[:status]
93
+ @error = attributes[:error]
94
+ @response = attributes[:payload]
95
+ @logs = attributes[:logs]
96
+ @metrics = attributes[:metrics]
97
+ end
98
+ end
99
+ end
data/lib/fal.rb CHANGED
@@ -2,6 +2,108 @@
2
2
 
3
3
  require "faraday"
4
4
  require "time"
5
+ require "json"
6
+ require "cgi"
7
+ require "uri"
8
+
9
+ require_relative "fal/version"
5
10
 
6
11
  module Fal
12
+ # Base error class for all fal-related errors.
13
+ class Error < StandardError; end
14
+ # Raised when a request is unauthorized (HTTP 401).
15
+ class UnauthorizedError < Error; end
16
+ # Raised when a requested resource is not found (HTTP 404).
17
+ class NotFoundError < Error; end
18
+ # Raised when the server returns an unexpected error (HTTP 5xx or others).
19
+ class ServerError < Error; end
20
+ # Raised when the client is misconfigured.
21
+ class ConfigurationError < Error; end
22
+ # Raised when access is forbidden (HTTP 403).
23
+ class ForbiddenError < Error; end
24
+
25
+ # Global configuration for the fal client.
26
+ class Configuration
27
+ DEFAULT_QUEUE_BASE = "https://queue.fal.run"
28
+ DEFAULT_SYNC_BASE = "https://fal.run"
29
+ DEFAULT_API_BASE = "https://api.fal.ai/v1"
30
+ DEFAULT_REQUEST_TIMEOUT = 120
31
+
32
+ # API key used for authenticating with fal endpoints.
33
+ # Defaults to ENV["FAL_KEY"].
34
+ # @return [String]
35
+ attr_accessor :api_key
36
+
37
+ # Base URL for fal queue endpoints.
38
+ # @return [String]
39
+ attr_accessor :queue_base
40
+
41
+ # Base URL for synchronous streaming endpoints (fal.run).
42
+ # @return [String]
43
+ attr_accessor :sync_base
44
+
45
+ # Base URL for platform API endpoints (api.fal.ai/v1).
46
+ # @return [String]
47
+ attr_accessor :api_base
48
+
49
+ # Timeout in seconds for opening and processing HTTP requests.
50
+ # @return [Integer]
51
+ attr_accessor :request_timeout
52
+
53
+ # Initialize configuration with sensible defaults.
54
+ # @return [Fal::Configuration]
55
+ def initialize
56
+ @api_key = ENV.fetch("FAL_KEY", nil)
57
+ @queue_base = DEFAULT_QUEUE_BASE
58
+ @sync_base = DEFAULT_SYNC_BASE
59
+ @api_base = DEFAULT_API_BASE
60
+ @request_timeout = DEFAULT_REQUEST_TIMEOUT
61
+ end
62
+ end
63
+
64
+ class << self
65
+ # The global configuration instance.
66
+ # @return [Fal::Configuration]
67
+ attr_accessor :configuration
68
+
69
+ # Configure the fal client.
70
+ # @yield [Fal::Configuration] the configuration object to mutate
71
+ # @return [void]
72
+ def configure
73
+ self.configuration ||= Configuration.new
74
+ yield(configuration)
75
+ end
76
+
77
+ # Global client accessor using the configured settings.
78
+ # @return [Fal::Client]
79
+ def client
80
+ configuration = self.configuration || Configuration.new
81
+ @client ||= Fal::Client.new(configuration)
82
+ end
83
+
84
+ # Deep symbolize keys of a Hash or Array.
85
+ # @param obj [Hash, Array]
86
+ # @return [Hash, Array]
87
+ def deep_symbolize_keys(obj)
88
+ case obj
89
+ when Hash
90
+ obj.each_with_object({}) do |(k, v), result|
91
+ key = k.is_a?(String) ? k.to_sym : k
92
+ result[key] = deep_symbolize_keys(v)
93
+ end
94
+ when Array
95
+ obj.map { |e| deep_symbolize_keys(e) }
96
+ else
97
+ obj
98
+ end
99
+ end
100
+ end
7
101
  end
102
+
103
+ require_relative "fal/client"
104
+ require_relative "fal/request"
105
+ require_relative "fal/stream"
106
+ require_relative "fal/webhook_request"
107
+ require_relative "fal/price"
108
+ require_relative "fal/price_estimate"
109
+ require_relative "fal/model"
@@ -0,0 +1,59 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Client
6
+ sig { returns(Fal::Configuration) }
7
+ def configuration; end
8
+
9
+ sig { params(value: Fal::Configuration).returns(Fal::Configuration) }
10
+ def configuration=(value); end
11
+
12
+ sig { params(configuration: T.nilable(Fal::Configuration)).void }
13
+ def initialize(configuration = nil); end
14
+
15
+ sig { params(path: String, payload: T.untyped, headers: T.untyped).returns(T.untyped) }
16
+ def post(path, payload = nil, headers: {}); end
17
+
18
+ sig do
19
+ params(
20
+ path: String,
21
+ payload: T.untyped,
22
+ on_data: T.proc.params(arg0: String, arg1: T.untyped).void
23
+ ).void
24
+ end
25
+ def post_stream(path, payload = nil, on_data:); end
26
+
27
+ sig { params(path: String, query: T.untyped, headers: T.untyped).returns(T.untyped) }
28
+ def get(path, query: nil, headers: {}); end
29
+
30
+ sig { params(path: String, query: T.untyped, headers: T.untyped).returns(T.untyped) }
31
+ def get_api(path, query: nil, headers: {}); end
32
+
33
+ sig { params(path: String, payload: T.untyped, headers: T.untyped).returns(T.untyped) }
34
+ def post_api(path, payload = nil, headers: {}); end
35
+
36
+ sig { params(path: String).returns(T.untyped) }
37
+ def put(path); end
38
+
39
+ sig { params(response: T.untyped).void }
40
+ def handle_error(response); end
41
+
42
+ private
43
+
44
+ sig { params(body: T.untyped).returns(T.untyped) }
45
+ def parse_json(body); end
46
+
47
+ sig { params(path: String).returns(String) }
48
+ def build_url(path); end
49
+
50
+ sig { params(path: String).returns(String) }
51
+ def build_sync_url(path); end
52
+
53
+ sig { params(path: String).returns(String) }
54
+ def build_api_url(path); end
55
+
56
+ sig { returns(T.untyped) }
57
+ def connection; end
58
+ end
59
+ end
data/rbi/fal/fal.rbi ADDED
@@ -0,0 +1,68 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Error < StandardError; end
6
+ class UnauthorizedError < Error; end
7
+ class NotFoundError < Error; end
8
+ class ServerError < Error; end
9
+ class ConfigurationError < Error; end
10
+ class ForbiddenError < Error; end
11
+
12
+ class Configuration
13
+ # api_key
14
+ sig { returns(T.nilable(String)) }
15
+ def api_key; end
16
+
17
+ sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
18
+ def api_key=(value); end
19
+
20
+ # queue_base
21
+ sig { returns(String) }
22
+ def queue_base; end
23
+
24
+ sig { params(value: String).returns(String) }
25
+ def queue_base=(value); end
26
+
27
+ # sync_base
28
+ sig { returns(String) }
29
+ def sync_base; end
30
+
31
+ sig { params(value: String).returns(String) }
32
+ def sync_base=(value); end
33
+
34
+ # api_base
35
+ sig { returns(String) }
36
+ def api_base; end
37
+
38
+ sig { params(value: String).returns(String) }
39
+ def api_base=(value); end
40
+
41
+ # request_timeout
42
+ sig { returns(Integer) }
43
+ def request_timeout; end
44
+
45
+ sig { params(value: Integer).returns(Integer) }
46
+ def request_timeout=(value); end
47
+
48
+ sig { void }
49
+ def initialize; end
50
+ end
51
+
52
+ class << self
53
+ sig { returns(Fal::Configuration) }
54
+ def configuration; end
55
+
56
+ sig { params(value: Fal::Configuration).returns(Fal::Configuration) }
57
+ def configuration=(value); end
58
+
59
+ sig { params(block: T.proc.params(arg0: Fal::Configuration).void).void }
60
+ def configure(&block); end
61
+
62
+ sig { returns(Fal::Client) }
63
+ def client; end
64
+
65
+ sig { params(obj: T.untyped).returns(T.untyped) }
66
+ def deep_symbolize_keys(obj); end
67
+ end
68
+ end
data/rbi/fal/model.rbi ADDED
@@ -0,0 +1,130 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Model
6
+ MODELS_PATH = T.let(T.unsafe(nil), String)
7
+
8
+ sig { returns(String) }
9
+ def endpoint_id; end
10
+
11
+ sig { returns(T.nilable(String)) }
12
+ def display_name; end
13
+
14
+ sig { returns(T.nilable(String)) }
15
+ def category; end
16
+
17
+ sig { returns(T.nilable(String)) }
18
+ def description; end
19
+
20
+ sig { returns(T.nilable(String)) }
21
+ def status; end
22
+
23
+ sig { returns(T.nilable(T::Array[String])) }
24
+ def tags; end
25
+
26
+ sig { returns(T.nilable(String)) }
27
+ def updated_at; end
28
+
29
+ sig { returns(T.nilable(T::Boolean)) }
30
+ def is_favorited; end
31
+
32
+ sig { returns(T.nilable(String)) }
33
+ def thumbnail_url; end
34
+
35
+ sig { returns(T.nilable(String)) }
36
+ def thumbnail_animated_url; end
37
+
38
+ sig { returns(T.nilable(String)) }
39
+ def model_url; end
40
+
41
+ sig { returns(T.nilable(String)) }
42
+ def github_url; end
43
+
44
+ sig { returns(T.nilable(String)) }
45
+ def license_type; end
46
+
47
+ sig { returns(T.nilable(String)) }
48
+ def date; end
49
+
50
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
51
+ def group; end
52
+
53
+ sig { returns(T.nilable(T::Boolean)) }
54
+ def highlighted; end
55
+
56
+ sig { returns(T.nilable(String)) }
57
+ def kind; end
58
+
59
+ sig { returns(T.nilable(T::Array[String])) }
60
+ def training_endpoint_ids; end
61
+
62
+ sig { returns(T.nilable(T::Array[String])) }
63
+ def inference_endpoint_ids; end
64
+
65
+ sig { returns(T.nilable(String)) }
66
+ def stream_url; end
67
+
68
+ sig { returns(T.nilable(Float)) }
69
+ def duration_estimate; end
70
+
71
+ sig { returns(T.nilable(T::Boolean)) }
72
+ def pinned; end
73
+
74
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
75
+ def openapi; end
76
+
77
+ sig { params(attributes: T.untyped, client: Fal::Client).void }
78
+ def initialize(attributes, client: Fal.client); end
79
+
80
+ sig { returns(T.nilable(Fal::Price)) }
81
+ def price; end
82
+
83
+ sig { params(input: T.untyped, webhook_url: T.nilable(String)).returns(Fal::Request) }
84
+ def run(input:, webhook_url: nil); end
85
+
86
+ class << self
87
+ sig { params(endpoint_id: String, client: Fal::Client).returns(T.nilable(Fal::Model)) }
88
+ def find_by(endpoint_id:, client: Fal.client); end
89
+
90
+ sig do
91
+ params(
92
+ client: Fal::Client,
93
+ query: T.nilable(String),
94
+ category: T.nilable(String),
95
+ status: T.nilable(String),
96
+ expand: T.nilable(T.any(String, T::Array[String])),
97
+ block: T.proc.params(arg0: Fal::Model).void
98
+ ).void
99
+ end
100
+ def each(client: Fal.client, query: nil, category: nil, status: nil, expand: nil, &block); end
101
+
102
+ sig do
103
+ params(
104
+ client: Fal::Client,
105
+ query: T.nilable(String),
106
+ category: T.nilable(String),
107
+ status: T.nilable(String),
108
+ expand: T.nilable(T.any(String, T::Array[String]))
109
+ ).returns(T::Array[Fal::Model])
110
+ end
111
+ def all(client: Fal.client, query: nil, category: nil, status: nil, expand: nil); end
112
+
113
+ sig do
114
+ params(
115
+ query: T.nilable(String),
116
+ category: T.nilable(String),
117
+ status: T.nilable(String),
118
+ expand: T.nilable(T.any(String, T::Array[String])),
119
+ client: Fal::Client
120
+ ).returns(T::Array[Fal::Model])
121
+ end
122
+ def search(query: nil, category: nil, status: nil, expand: nil, client: Fal.client); end
123
+ end
124
+
125
+ private
126
+
127
+ sig { params(attributes: T.untyped).void }
128
+ def reset_attributes(attributes); end
129
+ end
130
+ end
data/rbi/fal/price.rbi ADDED
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Price
6
+ MODELS_PATH = T.let(T.unsafe(nil), String)
7
+ PRICING_PATH = T.let(T.unsafe(nil), String)
8
+
9
+ module Unit
10
+ IMAGES = T.let(T.unsafe(nil), String)
11
+ VIDEOS = T.let(T.unsafe(nil), String)
12
+ MEGAPIXELS = T.let(T.unsafe(nil), String)
13
+ GPU_SECONDS = T.let(T.unsafe(nil), String)
14
+ GPU_MINUTES = T.let(T.unsafe(nil), String)
15
+ GPU_HOURS = T.let(T.unsafe(nil), String)
16
+ end
17
+
18
+ sig { returns(String) }
19
+ def endpoint_id; end
20
+
21
+ sig { returns(Float) }
22
+ def unit_price; end
23
+
24
+ sig { returns(String) }
25
+ def unit; end
26
+
27
+ sig { returns(String) }
28
+ def currency; end
29
+
30
+ sig { params(attributes: T.untyped, client: Fal::Client).void }
31
+ def initialize(attributes, client: Fal.client); end
32
+
33
+ class << self
34
+ sig { params(endpoint_id: String, client: Fal::Client).returns(T.nilable(Fal::Price)) }
35
+ def find_by(endpoint_id:, client: Fal.client); end
36
+
37
+ sig { params(client: Fal::Client, block: T.proc.params(arg0: Fal::Price).void).void }
38
+ def each(client: Fal.client, &block); end
39
+
40
+ sig { params(client: Fal::Client).returns(T::Array[Fal::Price]) }
41
+ def all(client: Fal.client); end
42
+ end
43
+
44
+ private
45
+
46
+ sig { params(attributes: T.untyped).void }
47
+ def reset_attributes(attributes); end
48
+ end
49
+ end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class PriceEstimate
6
+ ESTIMATE_PATH = T.let(T.unsafe(nil), String)
7
+
8
+ module EstimateType
9
+ HISTORICAL_API_PRICE = T.let(T.unsafe(nil), String)
10
+ UNIT_PRICE = T.let(T.unsafe(nil), String)
11
+ end
12
+
13
+ class Endpoint
14
+ sig { returns(String) }
15
+ def endpoint_id; end
16
+
17
+ sig { returns(T.nilable(Integer)) }
18
+ def call_quantity; end
19
+
20
+ sig { returns(T.nilable(Float)) }
21
+ def unit_quantity; end
22
+
23
+ sig do
24
+ params(
25
+ endpoint_id: String,
26
+ call_quantity: T.nilable(Integer),
27
+ unit_quantity: T.nilable(Float)
28
+ ).void
29
+ end
30
+ def initialize(endpoint_id:, call_quantity: nil, unit_quantity: nil); end
31
+ end
32
+
33
+ sig { returns(String) }
34
+ def estimate_type; end
35
+
36
+ sig { returns(Float) }
37
+ def total_cost; end
38
+
39
+ sig { returns(String) }
40
+ def currency; end
41
+
42
+ sig { params(attributes: T.untyped, client: Fal::Client).void }
43
+ def initialize(attributes, client: Fal.client); end
44
+
45
+ class << self
46
+ sig do
47
+ params(
48
+ estimate_type: String,
49
+ endpoints: T::Array[T.any(Fal::PriceEstimate::Endpoint, T.untyped)],
50
+ client: Fal::Client
51
+ ).returns(Fal::PriceEstimate)
52
+ end
53
+ def create(estimate_type:, endpoints:, client: Fal.client); end
54
+ end
55
+
56
+ private
57
+
58
+ sig { params(attributes: T.untyped).void }
59
+ def reset_attributes(attributes); end
60
+ end
61
+ end
@@ -0,0 +1,88 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Request
6
+ sig { returns(String) }
7
+ def id; end
8
+
9
+ sig { returns(String) }
10
+ def status; end
11
+
12
+ sig { returns(T.nilable(Integer)) }
13
+ def queue_position; end
14
+
15
+ sig { returns(T.untyped) }
16
+ def logs; end
17
+
18
+ sig { returns(T.untyped) }
19
+ def response; end
20
+
21
+ sig { returns(String) }
22
+ def endpoint_id; end
23
+
24
+ sig do
25
+ params(
26
+ attributes: T.untyped,
27
+ endpoint_id: String,
28
+ client: Fal::Client
29
+ ).void
30
+ end
31
+ def initialize(attributes, endpoint_id:, client:); end
32
+
33
+ class << self
34
+ sig do
35
+ params(
36
+ endpoint_id: String,
37
+ input: T.untyped,
38
+ client: Fal::Client,
39
+ webhook_url: T.nilable(String)
40
+ ).returns(Fal::Request)
41
+ end
42
+ def create!(endpoint_id:, input:, client:, webhook_url: nil); end
43
+
44
+ sig do
45
+ params(
46
+ id: String,
47
+ endpoint_id: String,
48
+ client: Fal::Client,
49
+ logs: T::Boolean
50
+ ).returns(Fal::Request)
51
+ end
52
+ def find_by!(id:, endpoint_id:, client:, logs: false); end
53
+
54
+ sig do
55
+ params(
56
+ endpoint_id: String,
57
+ input: T.untyped,
58
+ client: Fal::Client,
59
+ block: T.nilable(T.proc.params(arg0: T.untyped).void)
60
+ ).returns(Fal::Request)
61
+ end
62
+ def stream!(endpoint_id:, input:, client: Fal.client, &block); end
63
+ end
64
+
65
+ sig { returns(String) }
66
+ def endpoint_id_without_subpath; end
67
+
68
+ sig { params(logs: T::Boolean).returns(Fal::Request) }
69
+ def reload!(logs: false); end
70
+
71
+ sig { returns(T.untyped) }
72
+ def cancel!; end
73
+
74
+ sig { returns(T::Boolean) }
75
+ def in_queue?; end
76
+
77
+ sig { returns(T::Boolean) }
78
+ def in_progress?; end
79
+
80
+ sig { returns(T::Boolean) }
81
+ def completed?; end
82
+
83
+ private
84
+
85
+ sig { params(attributes: T.untyped).void }
86
+ def reset_attributes(attributes); end
87
+ end
88
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ class Stream
6
+ sig { returns(String) }
7
+ def path; end
8
+
9
+ sig do
10
+ params(
11
+ path: String,
12
+ input: T.untyped,
13
+ client: Fal::Client
14
+ ).void
15
+ end
16
+ def initialize(path:, input:, client: Fal.client); end
17
+
18
+ sig do
19
+ params(block: T.proc.params(arg0: T.untyped)
20
+ .void).void
21
+ end
22
+ def each(&block); end
23
+
24
+ class SSEDecoder
25
+ sig { void }
26
+ def initialize; end
27
+
28
+ sig { params(line: String).returns(T.untyped) }
29
+ def decode(line); end
30
+
31
+ private
32
+
33
+ sig { returns(T.untyped) }
34
+ def flush_event; end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Fal
5
+ VERSION = T.let(T.unsafe(nil), String)
6
+ end