altertable-lakehouse 0.5.1 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a603bff18558e212586e0bb27016f56c832266d7dedce34ee0a7cf5a6b820a5
4
- data.tar.gz: 4203dd0b9b090439722b8f51c8ad931048689e37ab1078738b541b0644f07427
3
+ metadata.gz: 772e340c84023d173342bd793b0467bb830a49d10b29b8c379addacb6beb2e8d
4
+ data.tar.gz: e3d23d9a428aa51acd9a87abfcab1723bb62f913915bedd43a5ab043b7ed7297
5
5
  SHA512:
6
- metadata.gz: 48b6b338259bdfa8ef5b65f967b76ec8ef79b48bba966eff4cd32c123c16ad94c7e3bb90d30ea09325d9b96f4313ea193f5e9bf639207f1969d0f640e869f44a
7
- data.tar.gz: 3459d13102d1a68eb09aaa8cdb969d0bb0ec7157061ceb83873eea9af545e0414f4702939d693eb32bcbe8a7c4208379041cb971b02de261f735801851304895
6
+ metadata.gz: f6194a61779c52bb92c03267b25ef12ef560d92ca1fed2fd63bb2238ee1292ae231ec700c163130a7f93509daf974efa53eb5bf12fb0066a6abcd07d670df4bf
7
+ data.tar.gz: 9b576b7bee48db81ade8f27e16656adf856c97e2c965f26e8f04e8ab2952a40365febc21832acf1e080314b08798189c788cfa7424a39ad6613581a29e8af1f2
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.5.1"
2
+ ".": "0.6.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.6.0](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse/v0.5.1...altertable-lakehouse/v0.6.0) (2026-06-30)
6
+
7
+
8
+ ### Features
9
+
10
+ * add configurable open timeout ([#41](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/41)) ([f568a96](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/f568a96a4fcc59dd91a622c6119779e1e9171bda))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **api:** expose upsert API ([#42](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/42)) ([fcda808](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/fcda808db2c52a526d848f56fa3509bdb2738310))
16
+
5
17
  ## [0.5.1](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse/v0.5.0...altertable-lakehouse/v0.5.1) (2026-05-29)
6
18
 
7
19
 
@@ -66,7 +78,7 @@ All notable changes to this project will be documented in this file.
66
78
 
67
79
  ### Added
68
80
  - Initial implementation of Altertable Lakehouse Ruby Client.
69
- - Support for `append`, `query` (streamed/accumulated), `upload`, `validate`.
81
+ - Support for `append`, `query` (streamed/accumulated), `upsert`, `validate`.
70
82
  - Support for `get_query`, `cancel_query`.
71
83
  - Typed request/response models.
72
84
  - Faraday-based HTTP client with retries.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- altertable-lakehouse (0.5.1)
4
+ altertable-lakehouse (0.6.0)
5
5
  base64
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -57,7 +57,8 @@ client = Altertable::Lakehouse::Client.new(
57
57
  username: "your_username",
58
58
  password: "your_password",
59
59
  base_url: "https://api.altertable.ai", # Optional
60
- timeout: 10 # Optional
60
+ timeout: 10, # Optional
61
+ open_timeout: 5 # Optional
61
62
  )
62
63
 
63
64
  # 2. Pre-encoded Basic Auth Token
@@ -144,7 +145,7 @@ end
144
145
 
145
146
  ### `upload`
146
147
 
147
- Uploads a file (CSV, Parquet, etc.) to a table.
148
+ Uploads a file (CSV, Parquet, etc.) to create, append, or overwrite a table.
148
149
 
149
150
  ```ruby
150
151
  File.open("data.csv", "rb") do |file|
@@ -152,13 +153,28 @@ File.open("data.csv", "rb") do |file|
152
153
  catalog: "main",
153
154
  schema: "public",
154
155
  table: "events",
155
- format: "csv",
156
156
  mode: "append",
157
157
  file_io: file
158
158
  )
159
159
  end
160
160
  ```
161
161
 
162
+ ### `upsert`
163
+
164
+ Upserts a file by matching rows on a primary key.
165
+
166
+ ```ruby
167
+ File.open("updates.csv", "rb") do |file|
168
+ client.upsert(
169
+ catalog: "main",
170
+ schema: "public",
171
+ table: "events",
172
+ primary_key: "id",
173
+ file_io: file
174
+ )
175
+ end
176
+ ```
177
+
162
178
  ### `get_query`
163
179
 
164
180
  Retrieves information about a query execution.
@@ -234,6 +250,7 @@ end
234
250
  | `basic_auth_token` | String | `ENV["ALTERTABLE_BASIC_AUTH_TOKEN"]` | Pre-encoded Basic Auth token |
235
251
  | `base_url` | String | `https://api.altertable.ai` | API base URL |
236
252
  | `timeout` | Integer | `10` | Request timeout in seconds |
253
+ | `open_timeout` | Integer | `5` | Connection timeout in seconds |
237
254
  | `user_agent` | String | `nil` | Custom User-Agent suffix |
238
255
 
239
256
  ## License
@@ -4,9 +4,10 @@ module Altertable
4
4
  Response = Struct.new(:status, :body, :headers)
5
5
 
6
6
  class Base
7
- def initialize(base_url:, timeout:, headers: {})
7
+ def initialize(base_url:, timeout:, open_timeout:, headers: {})
8
8
  @base_url = base_url
9
9
  @timeout = timeout
10
+ @open_timeout = open_timeout
10
11
  @headers = headers
11
12
  end
12
13
 
@@ -24,7 +25,7 @@ module Altertable
24
25
  end
25
26
 
26
27
  class FaradayAdapter < Base
27
- def initialize(base_url:, timeout:, headers: {})
28
+ def initialize(base_url:, timeout:, open_timeout:, headers: {})
28
29
  super
29
30
  require "faraday"
30
31
  require "faraday/retry"
@@ -33,6 +34,7 @@ module Altertable
33
34
  @conn = Faraday.new(url: @base_url) do |f|
34
35
  @headers.each { |k, v| f.headers[k] = v }
35
36
  f.options.timeout = @timeout
37
+ f.options.open_timeout = @open_timeout
36
38
  f.request :retry, max: 3, interval: 0.05, backoff_factor: 2
37
39
  f.adapter Faraday.default_adapter
38
40
  end
@@ -78,13 +80,13 @@ module Altertable
78
80
  end
79
81
 
80
82
  class HttpxAdapter < Base
81
- def initialize(base_url:, timeout:, headers: {})
83
+ def initialize(base_url:, timeout:, open_timeout:, headers: {})
82
84
  super
83
85
  require "httpx"
84
86
  # Configure retries plugin if available or implement manual retries?
85
87
  # Httpx has built-in retries via plugin.
86
88
  @client = HTTPX.plugin(:retries).with(
87
- timeout: { operation_timeout: @timeout },
89
+ timeout: { operation_timeout: @timeout, connect_timeout: @open_timeout },
88
90
  headers: @headers,
89
91
  base_url: @base_url
90
92
  )
@@ -133,7 +135,7 @@ module Altertable
133
135
  end
134
136
 
135
137
  class NetHttpAdapter < Base
136
- def initialize(base_url:, timeout:, headers: {})
138
+ def initialize(base_url:, timeout:, open_timeout:, headers: {})
137
139
  super
138
140
  require "net/http"
139
141
  require "uri"
@@ -164,7 +166,7 @@ module Altertable
164
166
  req.body = body if body
165
167
 
166
168
  # Net::HTTP start
167
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: @timeout, read_timeout: @timeout) do |http|
169
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: @open_timeout, read_timeout: @timeout) do |http|
168
170
  if block_given?
169
171
  http.request(req) do |response|
170
172
  # Stream the body if block is given
@@ -10,8 +10,9 @@ module Altertable
10
10
  class Client
11
11
  DEFAULT_BASE_URL = "https://api.altertable.ai"
12
12
  DEFAULT_TIMEOUT = 10
13
+ DEFAULT_OPEN_TIMEOUT = 5
13
14
 
14
- def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, user_agent: nil, adapter: nil, headers: {})
15
+ def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, open_timeout: nil, user_agent: nil, adapter: nil, headers: {})
15
16
  # 1. Try passed basic_auth_token
16
17
  # 2. Try passed username/password
17
18
  # 3. Try ENV["ALTERTABLE_BASIC_AUTH_TOKEN"]
@@ -31,15 +32,15 @@ module Altertable
31
32
 
32
33
  @base_url = base_url || DEFAULT_BASE_URL
33
34
  @timeout = timeout || DEFAULT_TIMEOUT
35
+ @open_timeout = open_timeout || DEFAULT_OPEN_TIMEOUT
34
36
  @user_agent = user_agent ? "AltertableRuby/#{VERSION} #{user_agent}" : "AltertableRuby/#{VERSION}"
35
37
 
36
38
  default_headers = {
37
39
  "Authorization" => @auth_header,
38
- "User-Agent" => @user_agent,
39
- "Content-Type" => "application/json"
40
+ "User-Agent" => @user_agent
40
41
  }
41
42
 
42
- @adapter = select_adapter(adapter, base_url: @base_url, timeout: @timeout, headers: default_headers.merge(headers))
43
+ @adapter = select_adapter(adapter, base_url: @base_url, timeout: @timeout, open_timeout: @open_timeout, headers: default_headers.merge(headers))
43
44
  end
44
45
 
45
46
  # POST /append
@@ -65,7 +66,7 @@ module Altertable
65
66
  buffer = ""
66
67
 
67
68
  # Use adapter's stream capability
68
- resp = @adapter.post("/query", body: req_body, headers: headers) do |chunk, _|
69
+ resp = @adapter.post("/query", body: req_body, headers: json_headers(headers)) do |chunk, _|
69
70
  buffer << chunk
70
71
  end
71
72
 
@@ -87,19 +88,32 @@ module Altertable
87
88
  end
88
89
 
89
90
  # POST /upload
90
- def upload(catalog:, schema:, table:, format:, mode:, file_io:, primary_key: nil, headers: {})
91
+ def upload(catalog:, schema:, table:, mode:, file_io:, headers: {})
91
92
  params = {
92
93
  catalog: catalog,
93
94
  schema: schema,
94
95
  table: table,
95
- format: format,
96
96
  mode: mode
97
97
  }
98
- params[:primary_key] = primary_key if primary_key
99
98
 
100
99
  body = file_io.respond_to?(:read) ? file_io.read : file_io
101
100
 
102
- resp = @adapter.post("/upload", body: body, params: params, headers: headers.merge("Content-Type" => "application/octet-stream"))
101
+ resp = @adapter.post("/upload", body: body, params: params, headers: headers)
102
+ handle_response(resp)
103
+ end
104
+
105
+ # POST /upsert
106
+ def upsert(catalog:, schema:, table:, primary_key:, file_io:, headers: {})
107
+ params = {
108
+ catalog: catalog,
109
+ schema: schema,
110
+ table: table,
111
+ primary_key: primary_key
112
+ }
113
+
114
+ body = file_io.respond_to?(:read) ? file_io.read : file_io
115
+
116
+ resp = @adapter.post("/upsert", body: body, params: params, headers: headers)
103
117
  handle_response(resp)
104
118
  end
105
119
 
@@ -187,11 +201,15 @@ module Altertable
187
201
  method, path,
188
202
  body: encode_request_body(body),
189
203
  params: query || {},
190
- headers: headers
204
+ headers: body.nil? ? headers : json_headers(headers)
191
205
  )
192
206
  handle_response(resp)
193
207
  end
194
208
 
209
+ def json_headers(headers)
210
+ { "Content-Type" => "application/json" }.merge(headers)
211
+ end
212
+
195
213
  def encode_request_body(body)
196
214
  case body
197
215
  when Hash, Array
@@ -1,5 +1,5 @@
1
1
  module Altertable
2
2
  module Lakehouse
3
- VERSION = "0.5.1"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -53,6 +53,7 @@ module Altertable
53
53
  class Client
54
54
  DEFAULT_BASE_URL = T.let(T.unsafe(nil), String)
55
55
  DEFAULT_TIMEOUT = T.let(T.unsafe(nil), Integer)
56
+ DEFAULT_OPEN_TIMEOUT = T.let(T.unsafe(nil), Integer)
56
57
 
57
58
  sig do
58
59
  params(
@@ -61,12 +62,13 @@ module Altertable
61
62
  basic_auth_token: T.nilable(String),
62
63
  base_url: T.nilable(String),
63
64
  timeout: T.nilable(T.any(Integer, Float)),
65
+ open_timeout: T.nilable(T.any(Integer, Float)),
64
66
  user_agent: T.nilable(String),
65
67
  adapter: T.nilable(Symbol),
66
68
  headers: T::Hash[String, String]
67
69
  ).void
68
70
  end
69
- def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, user_agent: nil, adapter: nil, headers: {}); end
71
+ def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, open_timeout: nil, user_agent: nil, adapter: nil, headers: {}); end
70
72
 
71
73
  sig do
72
74
  params(
@@ -94,14 +96,24 @@ module Altertable
94
96
  catalog: String,
95
97
  schema: String,
96
98
  table: String,
97
- format: String,
98
99
  mode: String,
99
100
  file_io: T.untyped,
100
- primary_key: T.nilable(String),
101
101
  headers: T::Hash[String, String]
102
102
  ).returns(T.untyped)
103
103
  end
104
- def upload(catalog:, schema:, table:, format:, mode:, file_io:, primary_key: nil, headers: {}); end
104
+ def upload(catalog:, schema:, table:, mode:, file_io:, headers: {}); end
105
+
106
+ sig do
107
+ params(
108
+ catalog: String,
109
+ schema: String,
110
+ table: String,
111
+ primary_key: String,
112
+ file_io: T.untyped,
113
+ headers: T::Hash[String, String]
114
+ ).returns(T.untyped)
115
+ end
116
+ def upsert(catalog:, schema:, table:, primary_key:, file_io:, headers: {}); end
105
117
 
106
118
  sig { params(query_id: String, headers: T::Hash[String, String]).returns(::Altertable::Lakehouse::Models::QueryLogResponse) }
107
119
  def get_query(query_id, headers: {}); end
@@ -664,8 +676,8 @@ module Altertable
664
676
  end
665
677
 
666
678
  class Base
667
- sig { params(base_url: String, timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
668
- def initialize(base_url:, timeout:, headers: {}); end
679
+ sig { params(base_url: String, timeout: T.any(Integer, Float), open_timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
680
+ def initialize(base_url:, timeout:, open_timeout:, headers: {}); end
669
681
 
670
682
  sig do
671
683
  params(
@@ -702,8 +714,8 @@ module Altertable
702
714
  end
703
715
 
704
716
  class FaradayAdapter < Base
705
- sig { params(base_url: String, timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
706
- def initialize(base_url:, timeout:, headers: {}); end
717
+ sig { params(base_url: String, timeout: T.any(Integer, Float), open_timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
718
+ def initialize(base_url:, timeout:, open_timeout:, headers: {}); end
707
719
 
708
720
  sig do
709
721
  params(
@@ -745,8 +757,8 @@ module Altertable
745
757
  end
746
758
 
747
759
  class HttpxAdapter < Base
748
- sig { params(base_url: String, timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
749
- def initialize(base_url:, timeout:, headers: {}); end
760
+ sig { params(base_url: String, timeout: T.any(Integer, Float), open_timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
761
+ def initialize(base_url:, timeout:, open_timeout:, headers: {}); end
750
762
 
751
763
  sig do
752
764
  params(
@@ -788,8 +800,8 @@ module Altertable
788
800
  end
789
801
 
790
802
  class NetHttpAdapter < Base
791
- sig { params(base_url: String, timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
792
- def initialize(base_url:, timeout:, headers: {}); end
803
+ sig { params(base_url: String, timeout: T.any(Integer, Float), open_timeout: T.any(Integer, Float), headers: T::Hash[String, String]).void }
804
+ def initialize(base_url:, timeout:, open_timeout:, headers: {}); end
793
805
 
794
806
  sig do
795
807
  params(
@@ -12,9 +12,10 @@ module Altertable
12
12
  class Base
13
13
  @base_url: String
14
14
  @timeout: Integer | Float
15
+ @open_timeout: Integer | Float
15
16
  @headers: ::Hash[String, String]
16
17
 
17
- def initialize: (base_url: String, timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
18
+ def initialize: (base_url: String, timeout: Integer | Float, open_timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
18
19
  def get: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
19
20
  def post: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
20
21
  def delete: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
@@ -23,7 +24,7 @@ module Altertable
23
24
  class FaradayAdapter < Base
24
25
  @conn: untyped
25
26
 
26
- def initialize: (base_url: String, timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
27
+ def initialize: (base_url: String, timeout: Integer | Float, open_timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
27
28
  def get: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
28
29
  def post: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
29
30
  def delete: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
@@ -36,7 +37,7 @@ module Altertable
36
37
  class HttpxAdapter < Base
37
38
  @client: untyped
38
39
 
39
- def initialize: (base_url: String, timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
40
+ def initialize: (base_url: String, timeout: Integer | Float, open_timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
40
41
  def get: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
41
42
  def post: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
42
43
  def delete: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
@@ -49,7 +50,7 @@ module Altertable
49
50
  class NetHttpAdapter < Base
50
51
  @uri: URI::HTTP | URI::HTTPS
51
52
 
52
- def initialize: (base_url: String, timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
53
+ def initialize: (base_url: String, timeout: Integer | Float, open_timeout: Integer | Float, ?headers: ::Hash[String, String]) -> void
53
54
  def get: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
54
55
  def post: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
55
56
  def delete: (String path, ?body: String?, ?params: ::Hash[Symbol | String, untyped], ?headers: ::Hash[String, String]) -> Response
@@ -4,11 +4,13 @@ module Altertable
4
4
  @auth_header: String
5
5
  @base_url: String
6
6
  @timeout: Integer | Float
7
+ @open_timeout: Integer | Float
7
8
  @user_agent: String
8
9
  @adapter: untyped
9
10
 
10
11
  DEFAULT_BASE_URL: String
11
12
  DEFAULT_TIMEOUT: Integer
13
+ DEFAULT_OPEN_TIMEOUT: Integer
12
14
 
13
15
  def initialize: (
14
16
  ?username: String?,
@@ -16,6 +18,7 @@ module Altertable
16
18
  ?basic_auth_token: String?,
17
19
  ?base_url: String?,
18
20
  ?timeout: Integer | Float?,
21
+ ?open_timeout: Integer | Float?,
19
22
  ?user_agent: String?,
20
23
  ?adapter: Symbol?,
21
24
  ?headers: ::Hash[String, String]
@@ -40,10 +43,17 @@ module Altertable
40
43
  catalog: String,
41
44
  schema: String,
42
45
  table: String,
43
- format: String,
44
46
  mode: String,
45
47
  file_io: untyped,
46
- ?primary_key: String?,
48
+ ?headers: ::Hash[String, String]
49
+ ) -> untyped
50
+
51
+ def upsert: (
52
+ catalog: String,
53
+ schema: String,
54
+ table: String,
55
+ primary_key: String,
56
+ file_io: untyped,
47
57
  ?headers: ::Hash[String, String]
48
58
  ) -> untyped
49
59
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: altertable-lakehouse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Altertable AI