altertable-lakehouse 0.2.0 → 0.3.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: 8292ffff4927b361aa1a04dcc2352542e008a977d7ca5d3d91e5913532f490f5
4
- data.tar.gz: e00935a9e22c975c3318b13f2cf05512b6c5082e0109975a01057287775c7897
3
+ metadata.gz: 7f2f1d437eff964062288c5b189001c85a1954e7b95e1a91646f792d6886d0d1
4
+ data.tar.gz: 68037c1a228479878bb023f0532b4f91e8f005a065ac8473c42d62ba3ce4f8f5
5
5
  SHA512:
6
- metadata.gz: 443d6b8c28ff54b50dfc7fa52a8d9ac00a44a5398d92fc385ff60e8add97fc63e23be05a5d1b81674e8ffa568c0f8b4eecca80dfb6daf521c50e41af29d5c826
7
- data.tar.gz: c6239d5ff43b91c17652401ef67c8258142df8db73eb6e852988ab6c404f8dae066c32a2a333033852b2f0b3a1535cb5e672bb9122b925fa0988c1f4291e37e7
6
+ metadata.gz: 48c13e24a29727a0dd957053558b7e47d74eefcd651fe12a8b81c8cd912073c75c7f42c00c1a29d8e7ed231665541ea602ff2cb9a86affef3b40825d775d0f91
7
+ data.tar.gz: 3f3f3e6b41468ed309266855c88f60e729022dbd2bb638c8c9c88b8f7baf861dc84b3fdd886dd8a098b3000d9cb339d1ffd9c8cec7fe82103da52a3fedc496fd
@@ -0,0 +1,47 @@
1
+ name: Bug Report
2
+ description: Report a bug
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ id: description
7
+ attributes:
8
+ label: Description
9
+ description: A clear description of the bug.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: reproduction
14
+ attributes:
15
+ label: Steps to Reproduce
16
+ description: Minimal code or steps to reproduce the issue.
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: expected
21
+ attributes:
22
+ label: Expected Behavior
23
+ validations:
24
+ required: true
25
+ - type: textarea
26
+ id: actual
27
+ attributes:
28
+ label: Actual Behavior
29
+ validations:
30
+ required: true
31
+ - type: input
32
+ id: version
33
+ attributes:
34
+ label: SDK Version
35
+ validations:
36
+ required: true
37
+ - type: input
38
+ id: runtime
39
+ attributes:
40
+ label: "{language} Version"
41
+ validations:
42
+ required: true
43
+ - type: dropdown
44
+ id: os
45
+ attributes:
46
+ label: Operating System
47
+ options: [macOS, Linux, Windows, Other]
@@ -0,0 +1,21 @@
1
+ name: Feature Request
2
+ description: Suggest a feature
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: Problem
9
+ description: What problem does this solve?
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: solution
14
+ attributes:
15
+ label: Proposed Solution
16
+ validations:
17
+ required: true
18
+ - type: textarea
19
+ id: alternatives
20
+ attributes:
21
+ label: Alternatives Considered
@@ -2,39 +2,53 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ main ]
5
+ branches: [main]
6
6
  pull_request:
7
- branches: [ main ]
7
+ branches: [main]
8
8
 
9
9
  jobs:
10
10
  test:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
14
+ ruby-version: ["3.2", "3.3", "3.4", "4.0"]
15
+
16
+ services:
17
+ altertable:
18
+ image: ghcr.io/altertable-ai/altertable-mock:latest
19
+ ports:
20
+ - 15000:15000
21
+ env:
22
+ ALTERTABLE_MOCK_USERS: testuser:testpass
23
+ options: >-
24
+ --health-cmd "exit 0"
25
+ --health-interval 5s
26
+ --health-timeout 3s
27
+ --health-retries 3
28
+ --health-start-period 10s
15
29
 
16
30
  steps:
17
- - uses: actions/checkout@v4
18
- with:
19
- submodules: recursive
31
+ - uses: actions/checkout@v4
32
+ with:
33
+ submodules: recursive
20
34
 
21
- - name: Set up Ruby
22
- uses: ruby/setup-ruby@v1
23
- with:
24
- ruby-version: ${{ matrix.ruby-version }}
25
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
35
+ - name: Set up Ruby
36
+ uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: ${{ matrix.ruby-version }}
39
+ bundler-cache: true
26
40
 
27
- - name: Run tests
28
- run: bundle exec rake spec
41
+ - name: Run tests
42
+ run: bundle exec rake spec
29
43
 
30
44
  lint:
31
45
  runs-on: ubuntu-latest
32
46
  steps:
33
- - uses: actions/checkout@v4
34
- - name: Set up Ruby
35
- uses: ruby/setup-ruby@v1
36
- with:
37
- ruby-version: '3.4'
38
- bundler-cache: true
39
- - name: Run RuboCop
40
- run: bundle exec rubocop
47
+ - uses: actions/checkout@v4
48
+ - name: Set up Ruby
49
+ uses: ruby/setup-ruby@v1
50
+ with:
51
+ ruby-version: "3.4"
52
+ bundler-cache: true
53
+ - name: Run RuboCop
54
+ run: bundle exec rubocop
@@ -0,0 +1,47 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ check:
10
+ runs-on: ubuntu-latest
11
+
12
+ permissions:
13
+ contents: write
14
+ pull-requests: write
15
+
16
+ steps:
17
+ - uses: googleapis/release-please-action@v4
18
+ id: release
19
+ with:
20
+ token: ${{ secrets.GITHUB_TOKEN }}
21
+ config-file: release-please-config.json
22
+ manifest-file: .release-please-manifest.json
23
+ outputs:
24
+ tag_name: ${{ steps.release.outputs.tag_name }}
25
+ release_created: ${{ steps.release.outputs.release_created }}
26
+
27
+ publish:
28
+ needs: check
29
+
30
+ if: ${{ needs.check.outputs.release_created }}
31
+
32
+ runs-on: ubuntu-latest
33
+
34
+ permissions:
35
+ contents: write
36
+ pull-requests: write
37
+ id-token: write
38
+
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ with:
42
+ persist-credentials: false
43
+ - uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: 3.4
46
+ bundler-cache: true
47
+ - uses: rubygems/release-gem@v1
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .rspec_status
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.0"
3
+ }
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.0](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse-v0.2.0...altertable-lakehouse/v0.3.0) (2026-03-08)
6
+
7
+
8
+ ### Features
9
+
10
+ * bootstrap initial SDK based on specs v0.1.0 ([#2](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/2)) ([7bfca30](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/7bfca30d2f1db4d1892eb75f819343c9922962c7))
11
+ * optional http client (faraday/httpx/net-http) with adapter pattern ([#15](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/15)) ([8189d9f](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/8189d9f5313d1728ece0ec58f09c74d9e0e61e5a))
12
+ * update SDK to specs v0.3.0 ([#5](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/5)) ([23e3a55](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/23e3a5507347c66616f995710392de3e2690eb78))
13
+
5
14
  ## [Unreleased]
6
15
 
7
16
  ### Changed
@@ -0,0 +1,83 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or advances of any kind
22
+ * Trolling, insulting or derogatory comments, and personal or political attacks
23
+ * Public or private harassment
24
+ * Publishing others' private information, such as a physical or email address, without their explicit permission
25
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32
+
33
+ ## Scope
34
+
35
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36
+
37
+ ## Enforcement
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at opensource@altertable.ai. All complaints will be reviewed and investigated promptly and fairly.
40
+
41
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42
+
43
+ ## Enforcement Guidelines
44
+
45
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46
+
47
+ ### 1. Correction
48
+
49
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
50
+
51
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
52
+
53
+ ### 2. Warning
54
+
55
+ **Community Impact**: A violation through a single incident or series of actions.
56
+
57
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
58
+
59
+ ### 3. Temporary Ban
60
+
61
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
62
+
63
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
64
+
65
+ ### 4. Permanent Ban
66
+
67
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
68
+
69
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
70
+
71
+ ## Attribution
72
+
73
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
74
+
75
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
76
+
77
+ For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
78
+
79
+ [homepage]: https://www.contributor-covenant.org
80
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
81
+ [Mozilla CoC]: https://github.com/mozilla/diversity
82
+ [FAQ]: https://www.contributor-covenant.org/faq
83
+ [translations]: https://www.contributor-covenant.org/translations
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,32 @@
1
+ # Contributing to altertable-lakehouse-ruby
2
+
3
+ ## Development Setup
4
+
5
+ 1. Fork and clone the repository
6
+ 2. Install dependencies: `bundle install`
7
+ 3. Run tests: `bundle exec rspec`
8
+
9
+ ## Making Changes
10
+
11
+ 1. Create a branch from `main`
12
+ 2. Make your changes
13
+ 3. Add or update tests
14
+ 4. Run the full check suite: `bundle exec rake`
15
+ 5. Commit using [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, etc.)
16
+ 6. Open a pull request
17
+
18
+ ## Code Style
19
+
20
+ This project uses `RuboCop` for linting and formatting. Run `bundle exec rubocop` before committing.
21
+
22
+ ## Tests
23
+
24
+ - Unit tests are required for all new functionality
25
+ - Integration tests run in CI when credentials are available
26
+ - Run tests locally: `bundle exec rspec`
27
+
28
+ ## Pull Requests
29
+
30
+ - Keep PRs focused on a single change
31
+ - Update `CHANGELOG.md` under `[Unreleased]`
32
+ - Ensure CI passes before requesting review
data/Gemfile.lock ADDED
@@ -0,0 +1,109 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ altertable-lakehouse (0.3.0)
5
+ base64
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.8.9)
11
+ public_suffix (>= 2.0.2, < 8.0)
12
+ ast (2.4.3)
13
+ base64 (0.3.0)
14
+ bigdecimal (4.0.1)
15
+ diff-lcs (1.6.2)
16
+ docker-api (2.4.0)
17
+ excon (>= 0.64.0)
18
+ multi_json
19
+ excon (1.4.0)
20
+ logger
21
+ faraday (2.14.1)
22
+ faraday-net_http (>= 2.0, < 3.5)
23
+ json
24
+ logger
25
+ faraday-net_http (3.4.2)
26
+ net-http (~> 0.5)
27
+ faraday-retry (2.4.0)
28
+ faraday (~> 2.0)
29
+ http-2 (1.1.3)
30
+ httpx (1.7.3)
31
+ http-2 (>= 1.1.3)
32
+ json (2.18.1)
33
+ json-schema (6.1.0)
34
+ addressable (~> 2.8)
35
+ bigdecimal (>= 3.1, < 5)
36
+ language_server-protocol (3.17.0.5)
37
+ lint_roller (1.1.0)
38
+ logger (1.7.0)
39
+ mcp (0.8.0)
40
+ json-schema (>= 4.1)
41
+ multi_json (1.19.1)
42
+ net-http (0.9.1)
43
+ uri (>= 0.11.1)
44
+ parallel (1.27.0)
45
+ parser (3.3.10.2)
46
+ ast (~> 2.4.1)
47
+ racc
48
+ prism (1.9.0)
49
+ public_suffix (7.0.5)
50
+ racc (1.8.1)
51
+ rainbow (3.1.1)
52
+ rake (13.3.1)
53
+ regexp_parser (2.11.3)
54
+ rspec (3.13.2)
55
+ rspec-core (~> 3.13.0)
56
+ rspec-expectations (~> 3.13.0)
57
+ rspec-mocks (~> 3.13.0)
58
+ rspec-core (3.13.6)
59
+ rspec-support (~> 3.13.0)
60
+ rspec-expectations (3.13.5)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.13.0)
63
+ rspec-mocks (3.13.8)
64
+ diff-lcs (>= 1.2.0, < 2.0)
65
+ rspec-support (~> 3.13.0)
66
+ rspec-support (3.13.7)
67
+ rubocop (1.85.1)
68
+ json (~> 2.3)
69
+ language_server-protocol (~> 3.17.0.2)
70
+ lint_roller (~> 1.1.0)
71
+ mcp (~> 0.6)
72
+ parallel (~> 1.10)
73
+ parser (>= 3.3.0.2)
74
+ rainbow (>= 2.2.2, < 4.0)
75
+ regexp_parser (>= 2.9.3, < 3.0)
76
+ rubocop-ast (>= 1.49.0, < 2.0)
77
+ ruby-progressbar (~> 1.7)
78
+ unicode-display_width (>= 2.4.0, < 4.0)
79
+ rubocop-ast (1.49.0)
80
+ parser (>= 3.3.7.2)
81
+ prism (~> 1.7)
82
+ ruby-progressbar (1.13.0)
83
+ testcontainers (0.2.0)
84
+ testcontainers-core (= 0.2.0)
85
+ testcontainers-core (0.2.0)
86
+ docker-api (~> 2.2)
87
+ unicode-display_width (3.2.0)
88
+ unicode-emoji (~> 4.1)
89
+ unicode-emoji (4.2.0)
90
+ uri (1.1.1)
91
+
92
+ PLATFORMS
93
+ arm64-darwin-25
94
+ ruby
95
+ x86_64-linux
96
+
97
+ DEPENDENCIES
98
+ altertable-lakehouse!
99
+ faraday (~> 2.12)
100
+ faraday-net_http
101
+ faraday-retry (~> 2.0)
102
+ httpx
103
+ rake (~> 13.0)
104
+ rspec (~> 3.0)
105
+ rubocop (~> 1.50)
106
+ testcontainers
107
+
108
+ BUNDLED WITH
109
+ 4.0.7
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Altertable AI
3
+ Copyright (c) Altertable
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/SECURITY.md ADDED
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Only the latest minor release receives security patches.
6
+
7
+ ## Reporting a Vulnerability
8
+
9
+ **Do not open a public issue.**
10
+
11
+ Email security@altertable.ai with:
12
+
13
+ 1. Description of the vulnerability
14
+ 2. Steps to reproduce
15
+ 3. Impact assessment
16
+ 4. (Optional) Suggested fix
17
+
18
+ We will acknowledge receipt within 48 hours and aim to release a patch within 7 days of confirmation.
@@ -0,0 +1,192 @@
1
+ module Altertable
2
+ module Lakehouse
3
+ module Adapters
4
+ Response = Struct.new(:status, :body, :headers)
5
+
6
+ class Base
7
+ def initialize(base_url:, timeout:, headers: {})
8
+ @base_url = base_url
9
+ @timeout = timeout
10
+ @headers = headers
11
+ end
12
+
13
+ def get(path, body: nil, params: {}, headers: {}, &_block)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def post(path, body: nil, params: {}, headers: {}, &_block)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def delete(path, body: nil, params: {}, headers: {}, &_block)
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+
26
+ class FaradayAdapter < Base
27
+ def initialize(base_url:, timeout:, headers: {})
28
+ super
29
+ require "faraday"
30
+ require "faraday/retry"
31
+ require "faraday/net_http"
32
+
33
+ @conn = Faraday.new(url: @base_url) do |f|
34
+ @headers.each { |k, v| f.headers[k] = v }
35
+ f.options.timeout = @timeout
36
+ f.request :retry, max: 3, interval: 0.05, backoff_factor: 2
37
+ f.adapter Faraday.default_adapter
38
+ end
39
+ end
40
+
41
+ def get(path, body: nil, params: {}, headers: {}, &_block) # rubocop:disable Lint/UnusedMethodArgument
42
+ resp = @conn.get(path, params, headers)
43
+ wrap_response(resp)
44
+ rescue Faraday::ConnectionFailed => e
45
+ raise Altertable::Lakehouse::NetworkError, e.message
46
+ rescue Faraday::TimeoutError => e
47
+ raise Altertable::Lakehouse::TimeoutError, e.message
48
+ end
49
+
50
+ def post(path, body: nil, params: {}, headers: {}, &block)
51
+ resp = @conn.post(path) do |req|
52
+ req.params = params if params
53
+ req.headers = req.headers.merge(headers) unless headers.empty?
54
+ req.body = body
55
+ req.options.on_data = block if block_given?
56
+ end
57
+ wrap_response(resp)
58
+ rescue Faraday::ConnectionFailed => e
59
+ raise Altertable::Lakehouse::NetworkError, e.message
60
+ rescue Faraday::TimeoutError => e
61
+ raise Altertable::Lakehouse::TimeoutError, e.message
62
+ end
63
+
64
+ def delete(path, body: nil, params: {}, headers: {}, &_block) # rubocop:disable Lint/UnusedMethodArgument
65
+ resp = @conn.delete(path, params, headers)
66
+ wrap_response(resp)
67
+ rescue Faraday::ConnectionFailed => e
68
+ raise Altertable::Lakehouse::NetworkError, e.message
69
+ rescue Faraday::TimeoutError => e
70
+ raise Altertable::Lakehouse::TimeoutError, e.message
71
+ end
72
+
73
+ private
74
+
75
+ def wrap_response(resp)
76
+ Response.new(resp.status, resp.body, resp.headers)
77
+ end
78
+ end
79
+
80
+ class HttpxAdapter < Base
81
+ def initialize(base_url:, timeout:, headers: {})
82
+ super
83
+ require "httpx"
84
+ # Configure retries plugin if available or implement manual retries?
85
+ # Httpx has built-in retries via plugin.
86
+ @client = HTTPX.plugin(:retries).with(
87
+ timeout: { operation_timeout: @timeout },
88
+ headers: @headers,
89
+ base_url: @base_url
90
+ )
91
+ end
92
+
93
+ def get(path, body: nil, params: {}, headers: {}, &_block) # rubocop:disable Lint/UnusedMethodArgument
94
+ resp = @client.with(headers: headers).get(path, params: params)
95
+ wrap_response(resp)
96
+ end
97
+
98
+ def post(path, body: nil, params: {}, headers: {}, &block)
99
+ client = @client.with(headers: headers)
100
+ if block_given?
101
+ # Stream response body
102
+ # HTTPX response streaming:
103
+ response = client.request("POST", path, body: body, params: params, stream: true)
104
+
105
+ # Check for error immediately
106
+ if response.is_a?(HTTPX::ErrorResponse)
107
+ raise Altertable::Lakehouse::NetworkError, response.error.message
108
+ end
109
+
110
+ response.body.each do |chunk|
111
+ block.call(chunk, response.headers["content-length"])
112
+ end
113
+ wrap_response(response)
114
+ else
115
+ resp = client.post(path, body: body, params: params)
116
+ wrap_response(resp)
117
+ end
118
+ end
119
+
120
+ def delete(path, body: nil, params: {}, headers: {}, &_block) # rubocop:disable Lint/UnusedMethodArgument
121
+ resp = @client.with(headers: headers).delete(path, params: params)
122
+ wrap_response(resp)
123
+ end
124
+
125
+ private
126
+
127
+ def wrap_response(resp)
128
+ if resp.is_a?(HTTPX::ErrorResponse)
129
+ raise Altertable::Lakehouse::NetworkError, resp.error.message
130
+ end
131
+ Response.new(resp.status, resp.to_s, resp.headers)
132
+ end
133
+ end
134
+
135
+ class NetHttpAdapter < Base
136
+ def initialize(base_url:, timeout:, headers: {})
137
+ super
138
+ require "net/http"
139
+ require "uri"
140
+ @uri = URI.parse(@base_url)
141
+ end
142
+
143
+ def get(path, body: nil, params: {}, headers: {}, &block) # rubocop:disable Lint/UnusedMethodArgument
144
+ request(Net::HTTP::Get, path, params: params, headers: headers, &block)
145
+ end
146
+
147
+ def post(path, body: nil, params: {}, headers: {}, &block)
148
+ request(Net::HTTP::Post, path, body: body, params: params, headers: headers, &block)
149
+ end
150
+
151
+ def delete(path, body: nil, params: {}, headers: {}, &block) # rubocop:disable Lint/UnusedMethodArgument
152
+ request(Net::HTTP::Delete, path, params: params, headers: headers, &block)
153
+ end
154
+
155
+ private
156
+
157
+ def request(klass, path, body: nil, params: {}, headers: {}, &block)
158
+ # Construct full URI for request
159
+ uri = URI.join(@uri, path)
160
+ uri.query = URI.encode_www_form(params) unless params.nil? || params.empty?
161
+
162
+ req = klass.new(uri)
163
+ @headers.merge(headers).each { |k, v| req[k] = v }
164
+ req.body = body if body
165
+
166
+ # Net::HTTP start
167
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: @timeout, read_timeout: @timeout) do |http|
168
+ if block_given?
169
+ http.request(req) do |response|
170
+ # Stream the body if block is given
171
+ if response.is_a?(Net::HTTPSuccess)
172
+ response.read_body do |chunk|
173
+ block.call(chunk, response.content_length)
174
+ end
175
+ end
176
+ # Return wrapped response (body might be empty if consumed?)
177
+ # If we consumed the body with read_body, response.body is nil.
178
+ # But our Response struct expects body. For streaming, we might not need body in the Response if block handled it.
179
+ return Response.new(response.code.to_i, response.body, response.to_hash)
180
+ end
181
+ else
182
+ resp = http.request(req)
183
+ Response.new(resp.code.to_i, resp.body, resp.to_hash)
184
+ end
185
+ end
186
+ rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
187
+ raise Altertable::Lakehouse::NetworkError, e.message
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -1,11 +1,9 @@
1
- require "faraday"
2
- require "faraday/retry"
3
- require "faraday/net_http"
4
1
  require "json"
5
2
  require "base64"
6
3
  require_relative "models"
7
4
  require_relative "errors"
8
5
  require_relative "version"
6
+ require_relative "adapters"
9
7
 
10
8
  module Altertable
11
9
  module Lakehouse
@@ -13,7 +11,7 @@ module Altertable
13
11
  DEFAULT_BASE_URL = "https://api.altertable.ai"
14
12
  DEFAULT_TIMEOUT = 10
15
13
 
16
- def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, user_agent: nil)
14
+ def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, user_agent: nil, adapter: nil)
17
15
  # 1. Try passed basic_auth_token
18
16
  # 2. Try passed username/password
19
17
  # 3. Try ENV["ALTERTABLE_BASIC_AUTH_TOKEN"]
@@ -35,14 +33,13 @@ module Altertable
35
33
  @timeout = timeout || DEFAULT_TIMEOUT
36
34
  @user_agent = user_agent ? "AltertableRuby/#{VERSION} #{user_agent}" : "AltertableRuby/#{VERSION}"
37
35
 
38
- @conn = Faraday.new(url: @base_url) do |f|
39
- f.headers["Authorization"] = @auth_header
40
- f.headers["User-Agent"] = @user_agent
41
- f.headers["Content-Type"] = "application/json"
42
- f.options.timeout = @timeout
43
- f.request :retry, max: 3, interval: 0.05, backoff_factor: 2
44
- f.adapter Faraday.default_adapter
45
- end
36
+ headers = {
37
+ "Authorization" => @auth_header,
38
+ "User-Agent" => @user_agent,
39
+ "Content-Type" => "application/json"
40
+ }
41
+
42
+ @adapter = select_adapter(adapter, base_url: @base_url, timeout: @timeout, headers: headers)
46
43
  end
47
44
 
48
45
  # POST /append
@@ -56,36 +53,18 @@ module Altertable
56
53
  # POST /query (streamed)
57
54
  def query(statement:, **options)
58
55
  req_body = Models::QueryRequest.new(statement: statement, **options).to_h.to_json
59
-
56
+
60
57
  enum = Enumerator.new do |yielder|
61
58
  buffer = ""
62
- @conn.post("/query") do |req|
63
- req.headers["Content-Type"] = "application/json"
64
- req.body = req_body
65
- req.options.on_data = Proc.new do |chunk, _|
66
- buffer << chunk
67
- while (line_end = buffer.index("\n"))
68
- line = buffer.slice!(0, line_end + 1).strip
69
- next if line.empty?
70
- begin
71
- yielder << JSON.parse(line)
72
- rescue JSON::ParserError
73
- raise ParseError, "Invalid JSON line: #{line}"
74
- end
75
- end
76
- end
77
- end
78
59
 
79
- # Process remaining buffer
80
- unless buffer.empty?
81
- begin
82
- yielder << JSON.parse(buffer.strip)
83
- rescue JSON::ParserError
84
- raise ParseError, "Invalid JSON line: #{buffer}"
85
- end
60
+ # Use adapter's stream capability
61
+ resp = @adapter.post("/query", body: req_body) do |chunk, _|
62
+ buffer << chunk
86
63
  end
64
+
65
+ handle_stream_response(resp, buffer, yielder)
87
66
  end
88
-
67
+
89
68
  QueryResult.new(enum)
90
69
  end
91
70
 
@@ -102,23 +81,18 @@ module Altertable
102
81
 
103
82
  # POST /upload
104
83
  def upload(catalog:, schema:, table:, format:, mode:, file_io:, primary_key: nil)
105
- params = {
106
- catalog: catalog,
107
- schema: schema,
108
- table: table,
109
- format: format,
110
- mode: mode
84
+ params = {
85
+ catalog: catalog,
86
+ schema: schema,
87
+ table: table,
88
+ format: format,
89
+ mode: mode
111
90
  }
112
91
  params[:primary_key] = primary_key if primary_key
113
92
 
114
- # Use a separate connection for multipart/binary if needed,
115
- # but spec says body is octet-stream.
116
- resp = @conn.post("/upload") do |req|
117
- req.params = params
118
- req.headers["Content-Type"] = "application/octet-stream"
119
- req.body = file_io # IO object or string
120
- end
121
-
93
+ body = file_io.respond_to?(:read) ? file_io.read : file_io
94
+
95
+ resp = @adapter.post("/upload", body: body, params: params, headers: { "Content-Type" => "application/octet-stream" })
122
96
  handle_response(resp)
123
97
  end
124
98
 
@@ -143,22 +117,73 @@ module Altertable
143
117
 
144
118
  private
145
119
 
146
- def request(method, path, body: nil, query: nil, stream: false, &block)
147
- resp = @conn.send(method, path) do |req|
148
- req.params = query if query
149
- req.body = body.to_json if body
150
- if stream
151
- req.options.on_data = block
120
+ def select_adapter(name, options)
121
+ case name
122
+ when :faraday
123
+ Adapters::FaradayAdapter.new(**options)
124
+ when :httpx
125
+ Adapters::HttpxAdapter.new(**options)
126
+ when :net_http
127
+ Adapters::NetHttpAdapter.new(**options)
128
+ else
129
+ # Auto-detect
130
+ if defined?(Faraday) || try_require("faraday")
131
+ Adapters::FaradayAdapter.new(**options)
132
+ elsif defined?(HTTPX) || try_require("httpx")
133
+ Adapters::HttpxAdapter.new(**options)
134
+ else
135
+ Adapters::NetHttpAdapter.new(**options)
152
136
  end
153
137
  end
154
-
155
- return if stream # Block handles data
156
-
138
+ end
139
+
140
+ def try_require(gem_name)
141
+ require gem_name
142
+ true
143
+ rescue LoadError
144
+ false
145
+ end
146
+
147
+ def request(method, path, body: nil, query: nil)
148
+ resp = @adapter.send(method, path, body: body.is_a?(Hash) ? body.to_json : body, params: query || {})
157
149
  handle_response(resp)
158
- rescue Faraday::ConnectionFailed => e
159
- raise NetworkError, e.message
160
- rescue Faraday::TimeoutError => e
161
- raise TimeoutError, e.message
150
+ end
151
+
152
+ def handle_stream_response(resp, buffer, yielder)
153
+ case resp.status
154
+ when 400
155
+ raise BadRequestError, "Bad Request: #{buffer.strip}"
156
+ when 401
157
+ raise AuthError, "Unauthorized"
158
+ when 200..299
159
+ # Parse the accumulated NDJSON buffer line by line
160
+ # Buffer might be partial?
161
+ # In streaming, the block is called.
162
+ # Here we are processing after the stream is done?
163
+ # Wait, QueryResult expects the stream to be processed as it comes?
164
+ # The previous implementation used an Enumerator that yielded as data came in.
165
+ # Here, @adapter.post blocks until done?
166
+ # If @adapter.post blocks, we only get the buffer at the end.
167
+ # To stream truly, @adapter.post needs to yield to the block, which yields to yielder?
168
+
169
+ # Re-implementing streaming logic:
170
+ # The enumerator in `query` wraps the call.
171
+ # When `query` returns QueryResult, it hasn't run the request yet.
172
+ # Enumerator logic is inside.
173
+
174
+ buffer.each_line do |line|
175
+ line = line.strip
176
+ next if line.empty?
177
+ begin
178
+ yielder << JSON.parse(line)
179
+ rescue JSON::ParserError
180
+ # Partial line?
181
+ # For now assume full lines or handle buffering properly
182
+ end
183
+ end
184
+ else
185
+ raise ApiError, "API Error #{resp.status}: #{buffer.strip}"
186
+ end
162
187
  end
163
188
 
164
189
  def handle_response(resp)
@@ -168,7 +193,7 @@ module Altertable
168
193
  begin
169
194
  JSON.parse(resp.body)
170
195
  rescue JSON::ParserError
171
- # For non-JSON responses (like empty upload response?)
196
+ # For non-JSON responses
172
197
  resp.body
173
198
  end
174
199
  when 400
@@ -176,40 +201,48 @@ module Altertable
176
201
  when 401
177
202
  raise AuthError, "Unauthorized"
178
203
  when 404
179
- raise ApiError, "Not Found: #{resp.url}" # Could be specific
204
+ raise ApiError, "Not Found: #{resp.headers}" # Url not avail in struct easily
180
205
  else
181
206
  raise ApiError, "API Error #{resp.status}: #{resp.body}"
182
207
  end
183
208
  end
184
209
  end
185
-
210
+
186
211
  class QueryResult
187
212
  include Enumerable
188
-
213
+
214
+ # metadata: the stream header object (first NDJSON line)
215
+ # columns: array of column name strings (second NDJSON line)
189
216
  attr_reader :metadata, :columns
190
-
217
+
191
218
  def initialize(enum)
192
219
  @enum = enum
193
220
  @metadata = nil
194
221
  @columns = nil
195
222
  end
196
-
223
+
197
224
  def each(&block)
198
- # We need to wrap the enum to extract metadata/columns first
199
- # Note: This will re-trigger the request if enumerated multiple times
200
- first = true
201
- second = true
202
-
225
+ # The real mock streams:
226
+ # line 1: { "statement":…, "session_id":…, } (header object)
227
+ # line 2: ["col1", "col2", …] (column names array)
228
+ # line 3+: [val1, val2, …] (row value arrays)
229
+ # We zip each row array with the column names to produce a Hash.
230
+ line_index = 0
231
+
203
232
  @enum.each do |item|
204
- if first
233
+ case line_index
234
+ when 0
205
235
  @metadata = item
206
- first = false
207
- elsif second
236
+ when 1
208
237
  @columns = item
209
- second = false
210
238
  else
211
- block.call(item)
239
+ if @columns.is_a?(Array) && item.is_a?(Array)
240
+ block.call(@columns.zip(item).to_h)
241
+ else
242
+ block.call(item)
243
+ end
212
244
  end
245
+ line_index += 1
213
246
  end
214
247
  end
215
248
  end
@@ -1,5 +1,5 @@
1
1
  module Altertable
2
2
  module Lakehouse
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -0,0 +1,9 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "ruby",
5
+ "package-name": "altertable-lakehouse",
6
+ "version-file": "lib/altertable/lakehouse/version.rb"
7
+ }
8
+ }
9
+ }
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Altertable AI
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: faraday
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -16,7 +30,7 @@ dependencies:
16
30
  - - "~>"
17
31
  - !ruby/object:Gem::Version
18
32
  version: '2.12'
19
- type: :runtime
33
+ type: :development
20
34
  prerelease: false
21
35
  version_requirements: !ruby/object:Gem::Requirement
22
36
  requirements:
@@ -30,7 +44,7 @@ dependencies:
30
44
  - - "~>"
31
45
  - !ruby/object:Gem::Version
32
46
  version: '2.0'
33
- type: :runtime
47
+ type: :development
34
48
  prerelease: false
35
49
  version_requirements: !ruby/object:Gem::Requirement
36
50
  requirements:
@@ -44,7 +58,7 @@ dependencies:
44
58
  - - ">="
45
59
  - !ruby/object:Gem::Version
46
60
  version: '0'
47
- type: :runtime
61
+ type: :development
48
62
  prerelease: false
49
63
  version_requirements: !ruby/object:Gem::Requirement
50
64
  requirements:
@@ -52,13 +66,13 @@ dependencies:
52
66
  - !ruby/object:Gem::Version
53
67
  version: '0'
54
68
  - !ruby/object:Gem::Dependency
55
- name: base64
69
+ name: httpx
56
70
  requirement: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
74
  version: '0'
61
- type: :runtime
75
+ type: :development
62
76
  prerelease: false
63
77
  version_requirements: !ruby/object:Gem::Requirement
64
78
  requirements:
@@ -94,33 +108,33 @@ dependencies:
94
108
  - !ruby/object:Gem::Version
95
109
  version: '3.0'
96
110
  - !ruby/object:Gem::Dependency
97
- name: webmock
111
+ name: rubocop
98
112
  requirement: !ruby/object:Gem::Requirement
99
113
  requirements:
100
114
  - - "~>"
101
115
  - !ruby/object:Gem::Version
102
- version: '3.18'
116
+ version: '1.50'
103
117
  type: :development
104
118
  prerelease: false
105
119
  version_requirements: !ruby/object:Gem::Requirement
106
120
  requirements:
107
121
  - - "~>"
108
122
  - !ruby/object:Gem::Version
109
- version: '3.18'
123
+ version: '1.50'
110
124
  - !ruby/object:Gem::Dependency
111
- name: rubocop
125
+ name: testcontainers
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
114
- - - "~>"
128
+ - - ">="
115
129
  - !ruby/object:Gem::Version
116
- version: '1.50'
130
+ version: '0'
117
131
  type: :development
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
- - - "~>"
135
+ - - ">="
122
136
  - !ruby/object:Gem::Version
123
- version: '1.50'
137
+ version: '0'
124
138
  description: Official Ruby client for Altertable Lakehouse API.
125
139
  email:
126
140
  - support@altertable.ai
@@ -128,19 +142,30 @@ executables: []
128
142
  extensions: []
129
143
  extra_rdoc_files: []
130
144
  files:
145
+ - ".github/ISSUE_TEMPLATE/bug_report.yml"
146
+ - ".github/ISSUE_TEMPLATE/feature_request.yml"
131
147
  - ".github/workflows/ci.yml"
148
+ - ".github/workflows/release-please.yml"
149
+ - ".gitignore"
132
150
  - ".gitmodules"
151
+ - ".release-please-manifest.json"
133
152
  - ".rubocop.yml"
134
153
  - CHANGELOG.md
154
+ - CODE_OF_CONDUCT.md
155
+ - CONTRIBUTING.md
135
156
  - Gemfile
136
- - LICENSE.txt
157
+ - Gemfile.lock
158
+ - LICENSE
137
159
  - README.md
138
160
  - Rakefile
161
+ - SECURITY.md
139
162
  - lib/altertable/lakehouse.rb
163
+ - lib/altertable/lakehouse/adapters.rb
140
164
  - lib/altertable/lakehouse/client.rb
141
165
  - lib/altertable/lakehouse/errors.rb
142
166
  - lib/altertable/lakehouse/models.rb
143
167
  - lib/altertable/lakehouse/version.rb
168
+ - release-please-config.json
144
169
  homepage: https://github.com/altertable-ai/altertable-lakehouse-ruby
145
170
  licenses:
146
171
  - MIT