riveter-sdk 0.1.0 → 1.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/README.md +29 -5
- data/lib/riveter/client.rb +45 -10
- data/lib/riveter/errors.rb +12 -1
- data/lib/riveter/models.rb +169 -13
- data/lib/riveter/page.rb +83 -0
- data/lib/riveter/resources.rb +149 -24
- data/lib/riveter/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c11eb2e15de73602df663800326a931a9300daf4ccc615a761e761e1ed776a71
|
|
4
|
+
data.tar.gz: '092c593d7aac42080efd1ea487822e36eae8218f2095f3cbb706950048fb011b'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60c2971793274fdaa1ed1ed545ac3c0ce389ece4f408eee7e79131b3d0d4f59d2988460a1f90e432cb7d48ad4bb440b909b40fec7f5873ce4d75c445d7dd8184
|
|
7
|
+
data.tar.gz: 586e72eb77cdf4fd1818cfa75285a02bf9df60ef8331f4fd0542baf1a636be79e92fbca855957cc9741e3b01148417ed6dcb15333a5e0940e4099870eb7ad4ae
|
data/README.md
CHANGED
|
@@ -55,12 +55,35 @@ riveter.runs.list(status: "success").auto_paging_each do |run|
|
|
|
55
55
|
end
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
## Datasets
|
|
59
|
+
|
|
60
|
+
A dataset is a kept table: input rows you add, output columns an enrichment fills.
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
dataset = riveter.datasets.create(
|
|
64
|
+
name: "Wineries",
|
|
65
|
+
input_columns: ["Winery URL"],
|
|
66
|
+
rows: [{ "Winery URL" => "ridgewine.com" }, { "Winery URL" => "bonnydoonvineyard.com" }]
|
|
67
|
+
)
|
|
68
|
+
riveter.datasets.update(dataset.id, output: { "Owner" => { prompt: "The winery's current owner" } })
|
|
69
|
+
fill = riveter.enrich(dataset_id: dataset.id)
|
|
70
|
+
riveter.runs.wait_for_result(fill.id)
|
|
71
|
+
riveter.datasets.list_rows(dataset.id).auto_paging_each do |row|
|
|
72
|
+
puts [row.input["Winery URL"], row.cells["Owner"].value].join(": ")
|
|
73
|
+
end
|
|
74
|
+
csv = riveter.datasets.export(dataset.id)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`datasets.create(prompt: ..., count: ...)` generates the rows instead (paid; pass
|
|
78
|
+
`estimate_cost: true` first). `datasets.import_rows(id, csv)` appends a CSV.
|
|
79
|
+
|
|
58
80
|
## Surface
|
|
59
81
|
|
|
60
|
-
- `riveter.enrich(...)`, `riveter.quick_search(...)`, `riveter.scrape(...)`, `riveter.account`
|
|
82
|
+
- `riveter.enrich(...)`, `riveter.quick_search(...)`, `riveter.search_agent(...)`, `riveter.scrape(...)`, `riveter.account`
|
|
61
83
|
- `riveter.runs` — `get`, `result`, `stop`, `list`, `summary`, `wait_for_result`
|
|
62
|
-
- `riveter.enrichments` — `list`, `create`, `get`, `update`, `
|
|
63
|
-
- `riveter.datasets` — `
|
|
84
|
+
- `riveter.enrichments` — `list`, `create`, `get`, `update`, `delete`
|
|
85
|
+
- `riveter.datasets` — `list`, `create`, `get`, `update`, `delete`, `list_rows`, `add_rows`, `delete_rows`, `update_row`, `import_rows`, `build_rows`, `promote_column`, `export`
|
|
86
|
+
- `riveter.tools` — `list`
|
|
64
87
|
- `riveter.configured_datasets` — `build`
|
|
65
88
|
- `riveter.extractions` — `create`, `get`, `run`
|
|
66
89
|
- `riveter.monitors` — `create`, `list`, `get`, `update`, `runs`
|
|
@@ -71,7 +94,8 @@ via `#raw` / `#[]`.
|
|
|
71
94
|
## Errors and retries
|
|
72
95
|
|
|
73
96
|
API failures raise `Riveter::APIError` with `#status`, `#type` (`not_found`,
|
|
74
|
-
`insufficient_credits`,
|
|
97
|
+
`insufficient_credits`, `feature_not_enabled`, ...; the known values are
|
|
98
|
+
`Riveter::APIError::TYPES`), `#message`, and optional `#details`. Network failures raise
|
|
75
99
|
`Riveter::APIConnectionError` / `Riveter::APITimeoutError`. 429s are retried automatically
|
|
76
100
|
using the `X-RateLimit-Reset` header; 5xx and network failures are retried for GETs.
|
|
77
101
|
Configure with `max_retries:` and `timeout:`.
|
|
@@ -91,5 +115,5 @@ Riveter::Client.new(
|
|
|
91
115
|
|
|
92
116
|
```bash
|
|
93
117
|
bundle install
|
|
94
|
-
bundle exec
|
|
118
|
+
bundle exec ruby -Ilib -e "require 'riveter'"
|
|
95
119
|
```
|
data/lib/riveter/client.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Riveter
|
|
|
12
12
|
|
|
13
13
|
attr_reader :base_url, :timeout, :max_retries,
|
|
14
14
|
:runs, :enrichments, :datasets, :configured_datasets,
|
|
15
|
-
:extractions, :monitors
|
|
15
|
+
:extractions, :monitors, :tools
|
|
16
16
|
|
|
17
17
|
# api_key falls back to ENV["RIVETER_API_KEY"]; base_url falls back to
|
|
18
18
|
# ENV["RIVETER_BASE_URL"], then production. timeout (seconds) must exceed
|
|
@@ -34,9 +34,16 @@ module Riveter
|
|
|
34
34
|
@configured_datasets = Resources::ConfiguredDatasets.new(self)
|
|
35
35
|
@extractions = Resources::Extractions.new(self)
|
|
36
36
|
@monitors = Resources::Monitors.new(self)
|
|
37
|
+
@tools = Resources::Tools.new(self)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Start an enrichment run on rows of input data. (operationId: enrich)
|
|
41
|
+
# Rows come from `input:` (inline) or `dataset_id:` (a Dataset); column config from
|
|
42
|
+
# `enrichment_id:`, `output:`, or `prompt:` + `attributes:`. On a
|
|
43
|
+
# Dataset, `columns:` / `rows:` / `positions:` (or `targets:` boxes) scope the fill and
|
|
44
|
+
# `states:` names the cell states to re-run (`force: true` runs every cell).
|
|
45
|
+
# `estimate_cost: true` prices the run instead of starting it; `max_credits`
|
|
46
|
+
# refuses a run whose estimated maximum exceeds it. Same on every paid method.
|
|
40
47
|
def enrich(**params)
|
|
41
48
|
Run.new(request(:post, "/enrich", body: params))
|
|
42
49
|
end
|
|
@@ -46,6 +53,12 @@ module Riveter
|
|
|
46
53
|
Run.new(request(:post, "/quick_search", body: params))
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
# Ask one research question; long-polls up to `wait` seconds (default 50) and
|
|
57
|
+
# returns the answer in #output["result"]. (operationId: searchAgent)
|
|
58
|
+
def search_agent(**params)
|
|
59
|
+
Run.new(request(:post, "/search_agent", body: params))
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
# Scrape a webpage and return its text synchronously. (operationId: scrape)
|
|
50
63
|
def scrape(**params)
|
|
51
64
|
ScrapeResult.new(request(:post, "/scrape", body: params))
|
|
@@ -56,18 +69,22 @@ module Riveter
|
|
|
56
69
|
AccountInfo.new(request(:get, "/account"))
|
|
57
70
|
end
|
|
58
71
|
|
|
59
|
-
|
|
72
|
+
# `body` is sent as JSON; `raw_body:` + `content_type:` send a body as-is (CSV imports).
|
|
73
|
+
# `response_format:` is :json (an empty or 204 body is nil), :text, or :bytes.
|
|
74
|
+
def request(method, path, query: nil, body: nil, raw_body: nil, content_type: nil, response_format: :json,
|
|
75
|
+
timeout: nil)
|
|
60
76
|
uri = URI.parse(@base_url + path)
|
|
61
77
|
if query
|
|
62
78
|
compact = query.reject { |_key, value| value.nil? }
|
|
63
79
|
uri.query = URI.encode_www_form(compact) unless compact.empty?
|
|
64
80
|
end
|
|
65
81
|
|
|
82
|
+
encoded_body = encode_request_body(body, raw_body, content_type)
|
|
66
83
|
retry_on_failure = method == :get
|
|
67
84
|
attempt = 0
|
|
68
85
|
loop do
|
|
69
86
|
response = begin
|
|
70
|
-
perform_http_request(method, uri,
|
|
87
|
+
perform_http_request(method, uri, encoded_body, timeout || @timeout)
|
|
71
88
|
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
72
89
|
if retry_on_failure && attempt < @max_retries
|
|
73
90
|
sleep(backoff_seconds(attempt))
|
|
@@ -85,7 +102,7 @@ module Riveter
|
|
|
85
102
|
end
|
|
86
103
|
|
|
87
104
|
status = response.code.to_i
|
|
88
|
-
return
|
|
105
|
+
return read_success_body(response, response_format) if status.between?(200, 299)
|
|
89
106
|
|
|
90
107
|
if status == 429 && attempt < @max_retries
|
|
91
108
|
sleep(rate_limit_delay_seconds(response, attempt))
|
|
@@ -104,24 +121,42 @@ module Riveter
|
|
|
104
121
|
|
|
105
122
|
private
|
|
106
123
|
|
|
107
|
-
|
|
124
|
+
REQUEST_CLASSES = {
|
|
125
|
+
get: Net::HTTP::Get, post: Net::HTTP::Post, patch: Net::HTTP::Patch, delete: Net::HTTP::Delete
|
|
126
|
+
}.freeze
|
|
127
|
+
private_constant :REQUEST_CLASSES
|
|
128
|
+
|
|
129
|
+
def perform_http_request(method, uri, encoded_body, timeout)
|
|
108
130
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
109
131
|
http.use_ssl = uri.scheme == "https"
|
|
110
132
|
http.open_timeout = timeout
|
|
111
133
|
http.read_timeout = timeout
|
|
112
134
|
|
|
113
|
-
|
|
114
|
-
request = request_class.new(uri)
|
|
135
|
+
request = REQUEST_CLASSES.fetch(method).new(uri)
|
|
115
136
|
request["Authorization"] = "Bearer #{@api_key}"
|
|
116
137
|
request["User-Agent"] = "riveter-sdk-ruby/#{VERSION}"
|
|
117
|
-
if
|
|
118
|
-
request["Content-Type"] =
|
|
119
|
-
request.body =
|
|
138
|
+
if encoded_body
|
|
139
|
+
request["Content-Type"] = encoded_body[:content_type]
|
|
140
|
+
request.body = encoded_body[:content]
|
|
120
141
|
end
|
|
121
142
|
|
|
122
143
|
http.request(request)
|
|
123
144
|
end
|
|
124
145
|
|
|
146
|
+
def encode_request_body(body, raw_body, content_type)
|
|
147
|
+
return { content: raw_body, content_type: content_type || "application/octet-stream" } if raw_body
|
|
148
|
+
return nil unless body
|
|
149
|
+
|
|
150
|
+
{ content: JSON.generate(body.reject { |_key, value| value.nil? }), content_type: "application/json" }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def read_success_body(response, response_format)
|
|
154
|
+
return response.body.to_s.b if response_format == :bytes
|
|
155
|
+
return response.body.to_s if response_format == :text
|
|
156
|
+
|
|
157
|
+
parse_json_body(response)
|
|
158
|
+
end
|
|
159
|
+
|
|
125
160
|
def parse_json_body(response)
|
|
126
161
|
return nil if response.body.nil? || response.body.empty?
|
|
127
162
|
|
data/lib/riveter/errors.rb
CHANGED
|
@@ -10,8 +10,19 @@ module Riveter
|
|
|
10
10
|
# A request or wait helper exceeded its time budget.
|
|
11
11
|
class APITimeoutError < APIConnectionError; end
|
|
12
12
|
|
|
13
|
-
# A non-2xx HTTP response, normalized across both error body dialects.
|
|
13
|
+
# A non-2xx HTTP response, normalized across both error body dialects. #type is one of
|
|
14
|
+
# TYPES, or a type this SDK does not know yet.
|
|
14
15
|
class APIError < Error
|
|
16
|
+
# The error types the API documents. `feature_not_enabled` (403) means the account has
|
|
17
|
+
# not migrated to Datasets: stop retrying and open https://app.riveterhq.com/v2/datasets.
|
|
18
|
+
# The 409 trio (`dataset_in_use`, `enrichment_in_use`, `row_in_use`) means a fill is
|
|
19
|
+
# running: wait or stop it.
|
|
20
|
+
TYPES = %w[
|
|
21
|
+
bad_request validation not_found forbidden unauthorized feature_not_enabled
|
|
22
|
+
duplicate_run_key insufficient_credits credit_cap_exceeded not_implemented
|
|
23
|
+
dataset_in_use enrichment_in_use row_in_use payload_too_large rate_limited
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
15
26
|
FALLBACK_TYPES = {
|
|
16
27
|
400 => "bad_request",
|
|
17
28
|
401 => "unauthorized",
|
data/lib/riveter/models.rb
CHANGED
|
@@ -41,17 +41,32 @@ module Riveter
|
|
|
41
41
|
:completed_cells, :total_cells_expected, :not_found_cells
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Kickoff-time estimate. #maximum is the contract — a run never charges more.
|
|
45
|
+
class CreditEstimate < Model
|
|
46
|
+
field :minimum, :maximum, :charged_upfront
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The estimate next to what has been charged. #credits_remaining and
|
|
50
|
+
# #sufficient_credits are present on estimates and credit errors only.
|
|
51
|
+
class CreditDetail < Model
|
|
52
|
+
field :credits_used, :credits_remaining, :sufficient_credits
|
|
53
|
+
model_field :estimate, CreditEstimate
|
|
54
|
+
end
|
|
55
|
+
|
|
44
56
|
# The uniform run envelope returned by kickoffs and every /runs endpoint.
|
|
45
57
|
# Endpoint-specific kickoff extras (dataset_id, credits_charged, ...) are
|
|
46
|
-
# plain fields; #output is nil until the run finishes.
|
|
58
|
+
# plain fields; #output is nil until the run finishes. An `estimate_cost: true`
|
|
59
|
+
# kickoff returns this shape with #estimate_cost set, no #id, and the priced
|
|
60
|
+
# #credit_detail. #legacy ({"notice", "successor", "docs"}) is set when the
|
|
61
|
+
# kickoff took a legacy mode.
|
|
47
62
|
class Run < Model
|
|
48
|
-
field :id, :type, :status, :credits_used, :app_url, :result_url,
|
|
49
|
-
:started_at, :finished_at, :error, :enrichment_id, :enrichment_name,
|
|
63
|
+
field :id, :type, :status, :credits_used, :estimate_cost, :app_url, :result_url,
|
|
64
|
+
:started_at, :finished_at, :error, :stopped_reason, :enrichment_id, :enrichment_name,
|
|
50
65
|
:dataset_id, :extraction_id, :monitor_id, :webhook_url, :output,
|
|
51
|
-
:max_items, :
|
|
52
|
-
:
|
|
53
|
-
:validation_warning, :tier
|
|
66
|
+
:max_items, :configured_dataset_id, :credits_charged, :variables,
|
|
67
|
+
:validation_warning, :tier, :legacy
|
|
54
68
|
model_field :progress, RunProgress
|
|
69
|
+
model_field :credit_detail, CreditDetail
|
|
55
70
|
|
|
56
71
|
def finished?
|
|
57
72
|
%w[success stopped].include?(status)
|
|
@@ -60,7 +75,7 @@ module Riveter
|
|
|
60
75
|
|
|
61
76
|
class RunListItem < Model
|
|
62
77
|
field :id, :type, :status, :enrichment_id, :enrichment_name, :row_count,
|
|
63
|
-
:credits_used, :error, :created_at, :started_at, :finished_at,
|
|
78
|
+
:credits_used, :error, :stopped_reason, :created_at, :started_at, :finished_at,
|
|
64
79
|
:app_url, :result_url
|
|
65
80
|
end
|
|
66
81
|
|
|
@@ -72,32 +87,173 @@ module Riveter
|
|
|
72
87
|
field :counts
|
|
73
88
|
end
|
|
74
89
|
|
|
90
|
+
# Compact list-endpoint shape. Column configuration is on Enrichments#get. #status,
|
|
91
|
+
# #app_url, and #dataset_id (the dataset copy to fill instead) are set on legacy
|
|
92
|
+
# enrichments only.
|
|
75
93
|
class EnrichmentSummary < Model
|
|
76
|
-
field :id, :name, :status, :app_url, :
|
|
94
|
+
field :id, :name, :status, :app_url, :dataset_id, :column_count, :input_columns,
|
|
95
|
+
:output_columns, :column_names_truncated, :created_at, :updated_at
|
|
77
96
|
end
|
|
78
97
|
|
|
98
|
+
# An enrichment: #prompt, #input_columns, #output as an array of column hashes ("name" /
|
|
99
|
+
# "prompt" / "output_schema" / "tool"), #tools, the budget fields, and #version.
|
|
100
|
+
# #dataset_id / #app_url are set on Enrichments#create. A legacy enrichment's id answers
|
|
101
|
+
# its older shape (the legacy API docs, kept in #raw) with #legacy set.
|
|
79
102
|
class Enrichment < Model
|
|
80
|
-
field :id, :name, :
|
|
103
|
+
field :id, :name, :prompt, :input_columns, :output, :tools, :budget_credits_per_row,
|
|
104
|
+
:budget_locked, :version, :created_at, :updated_at, :app_url, :dataset_id, :legacy
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# One cell. #state is pending / running / ok / not_found / failed / manual / stale;
|
|
108
|
+
# #reason and #error are set on failed cells only.
|
|
109
|
+
class DatasetCell < Model
|
|
110
|
+
field :value, :state, :reason, :error, :sources
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A dataset row: #input by input column name, #cells by output column name
|
|
114
|
+
# (DatasetCell each). #position is 1-based; #credits is the row's charge across its runs.
|
|
115
|
+
class DatasetRow < Model
|
|
116
|
+
field :id, :position, :input, :credits, :not_found_reason, :created_at, :updated_at
|
|
117
|
+
|
|
118
|
+
def cells
|
|
119
|
+
(@raw["cells"] || {}).transform_values { |cell| DatasetCell.new(cell) }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A row as compact: true answers it: the values alone. #cells is each output column's value
|
|
124
|
+
# (nil when the cell has none). Address the row by #position elsewhere.
|
|
125
|
+
class CompactDatasetRow < Model
|
|
126
|
+
field :position, :input, :cells
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# One of a column's most common values (a string is cut at 200 characters) and its row count.
|
|
130
|
+
class DatasetColumnValueCount < Model
|
|
131
|
+
field :value, :count
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# One column's counts over every row: #states (output columns only: cells by state, pending /
|
|
135
|
+
# stale / failed / not_found / ok / manual), #filled, #distinct_values, the most common #values.
|
|
136
|
+
class DatasetColumnSummary < Model
|
|
137
|
+
field :name, :kind, :states, :filled, :distinct_values
|
|
138
|
+
|
|
139
|
+
def values
|
|
140
|
+
(@raw["values"] || []).map { |item| DatasetColumnValueCount.new(item) }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Per-column counts over every row of a dataset (Datasets#summary).
|
|
145
|
+
class DatasetSummary < Model
|
|
146
|
+
field :row_count
|
|
147
|
+
|
|
148
|
+
def columns
|
|
149
|
+
(@raw["columns"] || []).map { |item| DatasetColumnSummary.new(item) }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# A run on a dataset as the dataset lists it; #type is dataset_build or enrichment.
|
|
154
|
+
class DatasetRunSummary < Model
|
|
155
|
+
field :id, :type, :status, :created_at, :finished_at
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# A dataset (ds_...). #run is set on Datasets#create / #build_rows when a build or fill
|
|
159
|
+
# started; the column configuration is on Enrichments#get(enrichment_id).
|
|
160
|
+
class Dataset < Model
|
|
161
|
+
field :id, :name, :input_columns, :index_column, :enrichment_id, :output_columns,
|
|
162
|
+
:row_count, :rows_expired, :import_in_progress, :estimate_cost, :created_at, :updated_at
|
|
163
|
+
model_field :run, Run
|
|
164
|
+
model_field :credit_detail, CreditDetail
|
|
165
|
+
|
|
166
|
+
def runs
|
|
167
|
+
(@raw["runs"] || []).map { |run| DatasetRunSummary.new(run) }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class DatasetListItem < Model
|
|
172
|
+
field :id, :name, :input_column_count, :output_column_count, :row_count, :rows_expired,
|
|
173
|
+
:created_at, :updated_at
|
|
174
|
+
model_field :newest_run, DatasetRunSummary
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
class DatasetRowsAdded < Model
|
|
178
|
+
field :skipped_duplicates, :row_count
|
|
179
|
+
|
|
180
|
+
def rows
|
|
181
|
+
(@raw["rows"] || []).map { |row| DatasetRow.new(row) }
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
class DatasetRowsDeleted < Model
|
|
186
|
+
field :deleted, :row_count
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# #status is "done" when the rows landed in the call, "importing" when a job is appending
|
|
190
|
+
# them (Datasets#get reports #import_in_progress until it does).
|
|
191
|
+
class DatasetImportResult < Model
|
|
192
|
+
field :row_count
|
|
193
|
+
|
|
194
|
+
def status
|
|
195
|
+
import_hash["status"]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def rows
|
|
199
|
+
import_hash["rows"]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def rows_added
|
|
203
|
+
import_hash["rows_added"]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def skipped_duplicates
|
|
207
|
+
import_hash["skipped_duplicates"]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
def import_hash
|
|
213
|
+
@raw["import"] || {}
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class ToolCatalogEntry < Model
|
|
218
|
+
field :name, :credits_per_call, :price_note, :default, :requires_opt_in, :enabled,
|
|
219
|
+
:requires_api_key, :api_key_linked, :companions, :inputs, :input_groups, :returns
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Every tool with its price, whether this account may use it, and the budget defaults.
|
|
223
|
+
class ToolCatalog < Model
|
|
224
|
+
field :budget
|
|
225
|
+
|
|
226
|
+
def tools
|
|
227
|
+
(@raw["tools"] || []).map { |tool| ToolCatalogEntry.new(tool) }
|
|
228
|
+
end
|
|
81
229
|
end
|
|
82
230
|
|
|
83
231
|
class Extraction < Model
|
|
84
232
|
field :id, :name, :status, :app_url, :starting_url, :goal_description,
|
|
85
233
|
:output_record_json_schema, :required_keys, :locked,
|
|
86
234
|
:validation_passing, :discovered_at, :run_credits_required,
|
|
87
|
-
:credits_charged
|
|
235
|
+
:credits_charged, :estimate_cost
|
|
236
|
+
model_field :credit_detail, CreditDetail
|
|
88
237
|
end
|
|
89
238
|
|
|
239
|
+
# A monitor fills #dataset_id's #columns (every output column when nil) again on a
|
|
240
|
+
# schedule. #enrichment_name and #has_input belong to a monitor on a legacy enrichment;
|
|
241
|
+
# #legacy is true on such a list item and the notice hash on Monitors#get / #update. An
|
|
242
|
+
# `estimate_cost: true` create returns this shape with #estimate_cost set, no #id, and
|
|
243
|
+
# the priced #credit_detail for one tick.
|
|
90
244
|
class Monitor < Model
|
|
91
245
|
field :id, :name, :enabled, :cadence, :minute, :hour, :day_of_week,
|
|
92
246
|
:day_of_month, :timezone, :webhook_url, :alert_rule, :output_format,
|
|
93
|
-
:next_run_at, :schedule_summary, :
|
|
94
|
-
:created_at, :has_input
|
|
247
|
+
:next_run_at, :schedule_summary, :dataset_id, :dataset_name, :columns,
|
|
248
|
+
:enrichment_id, :enrichment_name, :created_at, :has_input, :legacy, :estimate_cost
|
|
249
|
+
model_field :credit_detail, CreditDetail
|
|
95
250
|
end
|
|
96
251
|
|
|
97
252
|
# Synchronous scrape result (legacy request_status format, per the spec).
|
|
98
253
|
class ScrapeResult < Model
|
|
99
254
|
field :request_status, :text, :url, :base_url_for_links, :status_code,
|
|
100
|
-
:possibly_blocked, :credit_used, :riveter_app_link
|
|
255
|
+
:possibly_blocked, :credit_used, :riveter_app_link, :estimate_cost
|
|
256
|
+
model_field :credit_detail, CreditDetail
|
|
101
257
|
end
|
|
102
258
|
|
|
103
259
|
class Credit < Model
|
data/lib/riveter/page.rb
CHANGED
|
@@ -1,6 +1,89 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Riveter
|
|
4
|
+
# One page of enrichments. #each iterates the page; #auto_paging_each walks
|
|
5
|
+
# every following page too.
|
|
6
|
+
class EnrichmentsPage
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_reader :enrichments, :pagination
|
|
10
|
+
|
|
11
|
+
def initialize(data, fetch_page)
|
|
12
|
+
data = data.is_a?(Hash) ? data : {}
|
|
13
|
+
@enrichments = (data["enrichments"] || []).map { |item| EnrichmentSummary.new(item) }
|
|
14
|
+
@pagination = Pagination.new(data["pagination"] || {})
|
|
15
|
+
@fetch_page = fetch_page
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def each(&block)
|
|
19
|
+
@enrichments.each(&block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def next_page?
|
|
23
|
+
(pagination.page || 1) < (pagination.total_pages || 1)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def next_page
|
|
27
|
+
EnrichmentsPage.new(@fetch_page.call((pagination.page || 1) + 1), @fetch_page)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def auto_paging_each(&block)
|
|
31
|
+
return enum_for(:auto_paging_each) unless block
|
|
32
|
+
|
|
33
|
+
page = self
|
|
34
|
+
loop do
|
|
35
|
+
page.enrichments.each(&block)
|
|
36
|
+
break unless page.next_page?
|
|
37
|
+
|
|
38
|
+
page = page.next_page
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# One page of dataset rows (cursor-paginated). #each iterates the page;
|
|
44
|
+
# #auto_paging_each walks every following page too. Rows are DatasetRow, or
|
|
45
|
+
# CompactDatasetRow for a compact: true read (row_class).
|
|
46
|
+
class DatasetRowsPage
|
|
47
|
+
include Enumerable
|
|
48
|
+
|
|
49
|
+
attr_reader :rows, :next_cursor, :row_count
|
|
50
|
+
|
|
51
|
+
def initialize(data, fetch_page, row_class: DatasetRow)
|
|
52
|
+
data = data.is_a?(Hash) ? data : {}
|
|
53
|
+
@rows = (data["rows"] || []).map { |item| row_class.new(item) }
|
|
54
|
+
@next_cursor = data["next_cursor"]
|
|
55
|
+
@row_count = data["row_count"]
|
|
56
|
+
@fetch_page = fetch_page
|
|
57
|
+
@row_class = row_class
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def each(&block)
|
|
61
|
+
@rows.each(&block)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def next_page?
|
|
65
|
+
!next_cursor.nil?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def next_page
|
|
69
|
+
raise Error, "No next page" unless next_page?
|
|
70
|
+
|
|
71
|
+
DatasetRowsPage.new(@fetch_page.call(next_cursor), @fetch_page, row_class: @row_class)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def auto_paging_each(&block)
|
|
75
|
+
return enum_for(:auto_paging_each) unless block
|
|
76
|
+
|
|
77
|
+
page = self
|
|
78
|
+
loop do
|
|
79
|
+
page.rows.each(&block)
|
|
80
|
+
break unless page.next_page?
|
|
81
|
+
|
|
82
|
+
page = page.next_page
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
4
87
|
# One page of runs. #each iterates the page; #auto_paging_each walks every
|
|
5
88
|
# following page too.
|
|
6
89
|
class RunsPage
|
data/lib/riveter/resources.rb
CHANGED
|
@@ -25,7 +25,8 @@ module Riveter
|
|
|
25
25
|
Run.new(@client.request(:post, "/runs/#{run_id}/stop"))
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# List the account's runs, newest first.
|
|
28
|
+
# List the account's runs, newest first. Params: type:, status:, enrichment_id:,
|
|
29
|
+
# dataset_id:, monitor_id:, created_after:, created_before:, page:, per_page:. (operationId: listRuns)
|
|
29
30
|
def list(**params)
|
|
30
31
|
fetch_page = ->(page) { @client.request(:get, "/runs", query: params.merge(page: page)) }
|
|
31
32
|
RunsPage.new(fetch_page.call(params[:page] || 1), fetch_page)
|
|
@@ -57,15 +58,20 @@ module Riveter
|
|
|
57
58
|
@client = client
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# Paginated compact list of enrichments: names, status, column names and
|
|
62
|
+
# counts. Params: status:, name:, page:, per_page:. (operationId: listEnrichments)
|
|
63
|
+
def list(**params)
|
|
64
|
+
fetch_page = ->(page) { @client.request(:get, "/enrichments", query: params.merge(page: page)) }
|
|
65
|
+
EnrichmentsPage.new(fetch_page.call(params[:page] || 1), fetch_page)
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
# Save an enrichment recipe (no run): `prompt:` + `output:` (column names, or hashes
|
|
69
|
+
# with name / prompt / output_schema / tool), optionally `name:`, `input_columns:`,
|
|
70
|
+
# `budget:`, `budget_locked:`, `only_tools:`, `additional_tools:`; an empty Dataset
|
|
71
|
+
# bound to it comes back as #dataset_id. `dataset_id:` alone takes a Dataset's own
|
|
72
|
+
# enrichment. (operationId: createEnrichment)
|
|
73
|
+
def create(**params)
|
|
74
|
+
Enrichment.new(@client.request(:post, "/enrichments", body: params))
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
# The enrichment's input columns and output column configuration. (operationId: getEnrichment)
|
|
@@ -73,32 +79,145 @@ module Riveter
|
|
|
73
79
|
Enrichment.new(@client.request(:get, "/enrichments/#{enrichment_id}"))
|
|
74
80
|
end
|
|
75
81
|
|
|
76
|
-
#
|
|
82
|
+
# Edit the recipe: `name:`, `prompt:`, `input_columns:`, `budget:`, `budget_locked:`,
|
|
83
|
+
# the tool lists, and `output:` column operations keyed by column name (add with
|
|
84
|
+
# prompt / output_schema / tool, rename, delete). (operationId: updateEnrichment)
|
|
77
85
|
def update(enrichment_id, **params)
|
|
78
|
-
@client.request(:patch, "/enrichments/#{enrichment_id}", body: params)
|
|
86
|
+
Enrichment.new(@client.request(:patch, "/enrichments/#{enrichment_id}", body: params))
|
|
79
87
|
end
|
|
80
88
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
# Delete a saved recipe; datasets that used it keep their cells. Refused (409) while a
|
|
90
|
+
# fill runs on it. (operationId: deleteEnrichment)
|
|
91
|
+
def delete(enrichment_id)
|
|
92
|
+
@client.request(:delete, "/enrichments/#{enrichment_id}")
|
|
93
|
+
nil
|
|
84
94
|
end
|
|
85
95
|
end
|
|
86
96
|
|
|
87
|
-
#
|
|
97
|
+
# Datasets (ds_...): kept tables of input rows and enriched cells. Fill the cells with
|
|
98
|
+
# Client#enrich(dataset_id:).
|
|
88
99
|
class Datasets
|
|
89
100
|
def initialize(client)
|
|
90
101
|
@client = client
|
|
91
102
|
end
|
|
92
103
|
|
|
93
|
-
#
|
|
94
|
-
def
|
|
95
|
-
|
|
104
|
+
# The account's datasets, newest first (up to 200). (operationId: listDatasets)
|
|
105
|
+
def list
|
|
106
|
+
response = @client.request(:get, "/datasets")
|
|
107
|
+
(response["datasets"] || []).map { |item| DatasetListItem.new(item) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Create a dataset: rows sent inline (`rows:`, ≤ 100 per call), generated from
|
|
111
|
+
# `prompt:` + `count:` (a paid list build, #run on the dataset; `estimate_cost: true`
|
|
112
|
+
# prices it), or empty (`name:` / `input_columns:` alone). `enrichment_id:` binds a
|
|
113
|
+
# saved recipe; `auto_fill: true` starts the fill once the rows land. (operationId: buildDataset)
|
|
114
|
+
def create(**params)
|
|
115
|
+
Dataset.new(@client.request(:post, "/datasets", body: params))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The dataset's columns, row count, and runs. (operationId: getDataset)
|
|
119
|
+
def get(dataset_id)
|
|
120
|
+
Dataset.new(@client.request(:get, "/datasets/#{dataset_id}"))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Rename (`name:`), move the index column (`index_column:`), add / rename / delete
|
|
124
|
+
# input and output columns (`input_columns:` / `output:` operations keyed by column
|
|
125
|
+
# name), and set the bound enrichment's `budget:` / `budget_locked:`, in one
|
|
126
|
+
# transaction. (operationId: updateDataset)
|
|
127
|
+
def update(dataset_id, **params)
|
|
128
|
+
Dataset.new(@client.request(:patch, "/datasets/#{dataset_id}", body: params))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Delete the dataset and its rows. Refused (409) while a run is active on it. (operationId: deleteDataset)
|
|
132
|
+
def delete(dataset_id)
|
|
133
|
+
@client.request(:delete, "/datasets/#{dataset_id}")
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# A page of rows in position order (`limit:`, `cursor:`, `updated_since:`);
|
|
138
|
+
# #auto_paging_each walks every following page. `columns:` (an Array of names) keeps only
|
|
139
|
+
# those columns; `compact: true` answers CompactDatasetRow (the values alone), so a
|
|
140
|
+
# 1,000-row page of one column is one small call. (operationId: listDatasetRows)
|
|
141
|
+
def list_rows(dataset_id, columns: nil, compact: false, **params)
|
|
142
|
+
query = params.merge(columns: columns && Array(columns).join(","), compact: compact ? "true" : nil)
|
|
143
|
+
fetch_page = lambda do |cursor|
|
|
144
|
+
@client.request(:get, "/datasets/#{dataset_id}/rows", query: query.merge(cursor: cursor))
|
|
145
|
+
end
|
|
146
|
+
row_class = compact ? CompactDatasetRow : DatasetRow
|
|
147
|
+
DatasetRowsPage.new(fetch_page.call(params[:cursor]), fetch_page, row_class: row_class)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Per-column counts over every row, no rows read: an output column's cells by state and its
|
|
151
|
+
# most common values; an input column's filled rows and values. `columns:` picks the columns,
|
|
152
|
+
# `top_values:` how many values per column (1–100, default 20). (operationId: getDatasetSummary)
|
|
153
|
+
def summary(dataset_id, columns: nil, top_values: nil)
|
|
154
|
+
DatasetSummary.new(
|
|
155
|
+
@client.request(
|
|
156
|
+
:get, "/datasets/#{dataset_id}/summary",
|
|
157
|
+
query: { columns: columns && Array(columns).join(","), top_values: top_values }
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Append rows (≤ 100 per call) keyed by input column name; duplicates on the index
|
|
163
|
+
# column are skipped. Free. (operationId: addDatasetRows)
|
|
164
|
+
def add_rows(dataset_id, rows)
|
|
165
|
+
DatasetRowsAdded.new(@client.request(:post, "/datasets/#{dataset_id}/rows", body: { rows: rows }))
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Remove up to 100 rows by `ids:` (row_...) and / or 1-based `positions:`. (operationId: deleteDatasetRows)
|
|
169
|
+
def delete_rows(dataset_id, ids: nil, positions: nil)
|
|
170
|
+
query = { ids: ids&.join(","), positions: positions&.join(",") }
|
|
171
|
+
DatasetRowsDeleted.new(@client.request(:delete, "/datasets/#{dataset_id}/rows", query: query))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Set a row's `input:` values and / or `cells:` by hand (nil clears one; cells land as
|
|
175
|
+
# manual). `row_id` is a row id (row_...) or a 1-based position. (operationId: updateDatasetRow)
|
|
176
|
+
def update_row(dataset_id, row_id, **params)
|
|
177
|
+
DatasetRow.new(@client.request(:patch, "/datasets/#{dataset_id}/rows/#{row_id}", body: params))
|
|
96
178
|
end
|
|
97
179
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
180
|
+
# Append a CSV whose headers are input and / or output column names (output values
|
|
181
|
+
# land as manual cells). Small files land in the call (#status "done"); big ones are
|
|
182
|
+
# appended by a job ("importing"). (operationId: importDatasetRows)
|
|
183
|
+
def import_rows(dataset_id, csv)
|
|
184
|
+
DatasetImportResult.new(
|
|
185
|
+
@client.request(:post, "/datasets/#{dataset_id}/rows/import", raw_body: csv, content_type: "text/csv")
|
|
186
|
+
)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Generate `count:` more rows from a prompt (paid; `estimate_cost: true` prices it).
|
|
190
|
+
# The build run is #run on the dataset. (operationId: buildDatasetRows)
|
|
191
|
+
def build_rows(dataset_id, **params)
|
|
192
|
+
Dataset.new(@client.request(:post, "/datasets/#{dataset_id}/rows/build", body: params))
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Turn an output column into an input column, every row's value moving with it. (operationId: promoteDatasetColumn)
|
|
196
|
+
def promote_column(dataset_id, column)
|
|
197
|
+
Dataset.new(@client.request(:post, "/datasets/#{dataset_id}/columns/promote", body: { column: column }))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# The grid as CSV (default) or JSON Lines text, or an Excel workbook
|
|
201
|
+
# (`output_format: "xlsx"`, a binary String). (operationId: exportDataset)
|
|
202
|
+
def export(dataset_id, output_format: "csv")
|
|
203
|
+
@client.request(
|
|
204
|
+
:get, "/datasets/#{dataset_id}/export",
|
|
205
|
+
query: { output_format: output_format },
|
|
206
|
+
response_format: output_format == "xlsx" ? :bytes : :text
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# The tool catalog the row agent draws from.
|
|
212
|
+
class Tools
|
|
213
|
+
def initialize(client)
|
|
214
|
+
@client = client
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Every tool with its price, whether this account may use it, its inputs, and the
|
|
218
|
+
# budget defaults. (operationId: listTools)
|
|
219
|
+
def list
|
|
220
|
+
ToolCatalog.new(@client.request(:get, "/tools"))
|
|
102
221
|
end
|
|
103
222
|
end
|
|
104
223
|
|
|
@@ -136,13 +255,18 @@ module Riveter
|
|
|
136
255
|
end
|
|
137
256
|
end
|
|
138
257
|
|
|
139
|
-
#
|
|
258
|
+
# Monitors: a dataset's output columns filled again on a schedule, with diffs and webhooks.
|
|
140
259
|
class Monitors
|
|
141
260
|
def initialize(client)
|
|
142
261
|
@client = client
|
|
143
262
|
end
|
|
144
263
|
|
|
145
|
-
# Create a monitor that
|
|
264
|
+
# Create a monitor that fills a dataset again on a schedule: `dataset_id:` (or
|
|
265
|
+
# `enrichment_id:` naming the dataset's enrichment), the cadence fields, optionally
|
|
266
|
+
# `columns:` (output column names each tick fills; every one when omitted),
|
|
267
|
+
# `alert_rule:` (each_run / on_diff / disabled), `output_format:` (current_only /
|
|
268
|
+
# include_diffs). Creation is free; `estimate_cost: true` prices ONE tick and
|
|
269
|
+
# `max_credits` refuses the monitor when that estimate is higher. (operationId: createMonitor)
|
|
146
270
|
def create(**params)
|
|
147
271
|
Monitor.new(@client.request(:post, "/monitors", body: params))
|
|
148
272
|
end
|
|
@@ -158,7 +282,8 @@ module Riveter
|
|
|
158
282
|
Monitor.new(@client.request(:get, "/monitors/#{monitor_id}"))
|
|
159
283
|
end
|
|
160
284
|
|
|
161
|
-
# Pause, resume, or
|
|
285
|
+
# Pause, resume, repoint the webhook, or change the `columns:` a tick fills
|
|
286
|
+
# (`columns: nil` means every output column again). (operationId: updateMonitor)
|
|
162
287
|
def update(monitor_id, **params)
|
|
163
288
|
Monitor.new(@client.request(:patch, "/monitors/#{monitor_id}", body: params))
|
|
164
289
|
end
|
data/lib/riveter/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: riveter-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Riveter
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-10-30 00:00:00.000000000 Z
|
|
11
12
|
dependencies: []
|
|
12
13
|
description: Client for the Riveter API — enrich data, build datasets, scrape pages,
|
|
13
14
|
and run web searches. https://docs.riveterhq.com
|
|
@@ -32,6 +33,7 @@ licenses:
|
|
|
32
33
|
metadata:
|
|
33
34
|
homepage_uri: https://docs.riveterhq.com
|
|
34
35
|
rubygems_mfa_required: 'true'
|
|
36
|
+
post_install_message:
|
|
35
37
|
rdoc_options: []
|
|
36
38
|
require_paths:
|
|
37
39
|
- lib
|
|
@@ -46,7 +48,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
46
48
|
- !ruby/object:Gem::Version
|
|
47
49
|
version: '0'
|
|
48
50
|
requirements: []
|
|
49
|
-
rubygems_version:
|
|
51
|
+
rubygems_version: 3.5.22
|
|
52
|
+
signing_key:
|
|
50
53
|
specification_version: 4
|
|
51
54
|
summary: Official Ruby SDK for the Riveter API
|
|
52
55
|
test_files: []
|