riveter-sdk 0.1.0__tar.gz
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.
- riveter_sdk-0.1.0/.gitignore +180 -0
- riveter_sdk-0.1.0/LICENSE +21 -0
- riveter_sdk-0.1.0/PKG-INFO +97 -0
- riveter_sdk-0.1.0/README.md +81 -0
- riveter_sdk-0.1.0/pyproject.toml +28 -0
- riveter_sdk-0.1.0/riveter/__init__.py +48 -0
- riveter_sdk-0.1.0/riveter/_client.py +205 -0
- riveter_sdk-0.1.0/riveter/_errors.py +63 -0
- riveter_sdk-0.1.0/riveter/_models.py +285 -0
- riveter_sdk-0.1.0/riveter/_pagination.py +39 -0
- riveter_sdk-0.1.0/riveter/_resources.py +349 -0
- riveter_sdk-0.1.0/riveter/_version.py +1 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Ruby/Rails
|
|
2
|
+
*.gem
|
|
3
|
+
*.rbc
|
|
4
|
+
/.config
|
|
5
|
+
/coverage/
|
|
6
|
+
/InstalledFiles
|
|
7
|
+
/pkg/
|
|
8
|
+
/spec/reports/
|
|
9
|
+
/spec/examples.txt
|
|
10
|
+
/test/tmp/
|
|
11
|
+
/test/version_tmp/
|
|
12
|
+
/tmp/
|
|
13
|
+
/.bundle/
|
|
14
|
+
/vendor/bundle
|
|
15
|
+
/lib/bundler/man/
|
|
16
|
+
.ruby-version
|
|
17
|
+
.ruby-gemset
|
|
18
|
+
.rvmrc
|
|
19
|
+
.rbenv-version
|
|
20
|
+
|
|
21
|
+
# Rails specific
|
|
22
|
+
*.sqlite3
|
|
23
|
+
*.sqlite3-journal
|
|
24
|
+
*.sqlite3-shm
|
|
25
|
+
*.sqlite3-wal
|
|
26
|
+
/db/*.sqlite3
|
|
27
|
+
/db/*.sqlite3-journal
|
|
28
|
+
/db/*.sqlite3-shm
|
|
29
|
+
/db/*.sqlite3-wal
|
|
30
|
+
/log/*
|
|
31
|
+
!/log/.keep
|
|
32
|
+
/storage/*
|
|
33
|
+
!/storage/.keep
|
|
34
|
+
/tmp/*
|
|
35
|
+
!/tmp/.keep
|
|
36
|
+
/public/assets
|
|
37
|
+
/public/packs
|
|
38
|
+
/public/packs-test
|
|
39
|
+
/node_modules
|
|
40
|
+
/yarn-error.log
|
|
41
|
+
yarn-debug.log*
|
|
42
|
+
.yarn-integrity
|
|
43
|
+
.byebug_history
|
|
44
|
+
config/master.key
|
|
45
|
+
config/credentials/*.key
|
|
46
|
+
|
|
47
|
+
# Node.js/React
|
|
48
|
+
node_modules/
|
|
49
|
+
npm-debug.log*
|
|
50
|
+
yarn-debug.log*
|
|
51
|
+
yarn-error.log*
|
|
52
|
+
.pnpm-debug.log*
|
|
53
|
+
lerna-debug.log*
|
|
54
|
+
dist/
|
|
55
|
+
build/
|
|
56
|
+
# code-executor Lambda packaging artifacts (built in CI, see deploy-code-executor-aws.yml)
|
|
57
|
+
service-code-executor-js/lambda-pkg/
|
|
58
|
+
service-code-executor-js/function.zip
|
|
59
|
+
.env.*
|
|
60
|
+
.env.local
|
|
61
|
+
.env.development.local
|
|
62
|
+
.env.development
|
|
63
|
+
.env.test.local
|
|
64
|
+
.env.production.local
|
|
65
|
+
.cache/
|
|
66
|
+
.vite/
|
|
67
|
+
*.tsbuildinfo
|
|
68
|
+
|
|
69
|
+
# Vite
|
|
70
|
+
public/vite-dev/
|
|
71
|
+
public/vite-dev/*
|
|
72
|
+
public/vite-dev*
|
|
73
|
+
public/vite/
|
|
74
|
+
public/vite/*
|
|
75
|
+
public/vite*
|
|
76
|
+
public/vite-dev/assets/
|
|
77
|
+
vite.config.mts*
|
|
78
|
+
|
|
79
|
+
# Package managers
|
|
80
|
+
package-lock.json
|
|
81
|
+
yarn.lock
|
|
82
|
+
pnpm-lock.yaml
|
|
83
|
+
|
|
84
|
+
# IDE/Editor files
|
|
85
|
+
.vscode/
|
|
86
|
+
.idea/
|
|
87
|
+
*.swp
|
|
88
|
+
*.swo
|
|
89
|
+
*~
|
|
90
|
+
.DS_Store
|
|
91
|
+
.DS_Store?
|
|
92
|
+
._*
|
|
93
|
+
.Spotlight-V100
|
|
94
|
+
.Trashes
|
|
95
|
+
ehthumbs.db
|
|
96
|
+
Thumbs.db
|
|
97
|
+
|
|
98
|
+
# Environment variables
|
|
99
|
+
.env
|
|
100
|
+
.env.example
|
|
101
|
+
.envrc
|
|
102
|
+
# MCP registry publish tokens (mcp-server)
|
|
103
|
+
.mcpregistry_*
|
|
104
|
+
|
|
105
|
+
# Logs
|
|
106
|
+
*.log
|
|
107
|
+
logs/
|
|
108
|
+
|
|
109
|
+
# Coverage
|
|
110
|
+
coverage/
|
|
111
|
+
.nyc_output
|
|
112
|
+
.coverage/
|
|
113
|
+
|
|
114
|
+
# Dependency directories
|
|
115
|
+
jspm_packages/
|
|
116
|
+
|
|
117
|
+
# Optional npm cache directory
|
|
118
|
+
.npm
|
|
119
|
+
|
|
120
|
+
# Optional eslint cache
|
|
121
|
+
.eslintcache
|
|
122
|
+
|
|
123
|
+
# Microbundle cache
|
|
124
|
+
.rpt2_cache/
|
|
125
|
+
.rts2_cache_cjs/
|
|
126
|
+
.rts2_cache_es/
|
|
127
|
+
.rts2_cache_umd/
|
|
128
|
+
|
|
129
|
+
# Optional REPL history
|
|
130
|
+
.node_repl_history
|
|
131
|
+
|
|
132
|
+
# Output of 'npm pack'
|
|
133
|
+
*.tgz
|
|
134
|
+
|
|
135
|
+
# Yarn Integrity file
|
|
136
|
+
.yarn-integrity
|
|
137
|
+
|
|
138
|
+
# dotenv environment variables file
|
|
139
|
+
.env.test
|
|
140
|
+
|
|
141
|
+
# parcel-bundler cache (https://parceljs.org/)
|
|
142
|
+
.parcel-cache
|
|
143
|
+
|
|
144
|
+
# Next.js build output
|
|
145
|
+
.next
|
|
146
|
+
|
|
147
|
+
# Nuxt.js build / generate output
|
|
148
|
+
.nuxt
|
|
149
|
+
dist
|
|
150
|
+
|
|
151
|
+
# Gatsby files
|
|
152
|
+
.cache/
|
|
153
|
+
|
|
154
|
+
# Storybook build outputs
|
|
155
|
+
.out
|
|
156
|
+
.storybook-out
|
|
157
|
+
|
|
158
|
+
# Temporary folders
|
|
159
|
+
tmp/
|
|
160
|
+
# Local agent/human scratch dumps (payload dumps, ad-hoc JSON, etc.)
|
|
161
|
+
tmp/scratch/
|
|
162
|
+
payload.json
|
|
163
|
+
temp/
|
|
164
|
+
script/dummy_data/
|
|
165
|
+
|
|
166
|
+
# SDKs (sdk/) — per-language build/test artifacts
|
|
167
|
+
sdk/python/.venv/
|
|
168
|
+
sdk/python/*.egg-info/
|
|
169
|
+
sdk/python/.pytest_cache/
|
|
170
|
+
__pycache__/
|
|
171
|
+
sdk/ruby/.bundle/
|
|
172
|
+
sdk/ruby/Gemfile.lock
|
|
173
|
+
|
|
174
|
+
# Vite Ruby
|
|
175
|
+
/public/vite*
|
|
176
|
+
node_modules
|
|
177
|
+
# Vite uses dotenv and suggests to ignore local-only env files. See
|
|
178
|
+
# https://vitejs.dev/guide/env-and-mode.html#env-files
|
|
179
|
+
*.local
|
|
180
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cody
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: riveter-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Riveter API — enrich data, build datasets, scrape pages, and run web searches
|
|
5
|
+
Project-URL: Homepage, https://docs.riveterhq.com
|
|
6
|
+
Project-URL: Documentation, https://docs.riveterhq.com
|
|
7
|
+
Author-email: Riveter <support@riveterhq.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,dataset,enrichment,riveter,scraping,sdk,web-search
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: httpx<1,>=0.24
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# riveter-sdk
|
|
18
|
+
|
|
19
|
+
Official Python SDK for the [Riveter API](https://docs.riveterhq.com) — enrich data,
|
|
20
|
+
build datasets, scrape pages, and run web searches.
|
|
21
|
+
|
|
22
|
+
Requires Python 3.9+. The import name is `riveter`.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install riveter-sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from riveter import Riveter
|
|
34
|
+
|
|
35
|
+
# Reads RIVETER_API_KEY from the environment when api_key is not passed.
|
|
36
|
+
# Get a key at https://app.riveterhq.com/settings/api
|
|
37
|
+
riveter = Riveter(api_key="YOUR_API_KEY")
|
|
38
|
+
|
|
39
|
+
run = riveter.enrich(
|
|
40
|
+
prompt="Research each company",
|
|
41
|
+
attributes=["CEO", "Employee Count"],
|
|
42
|
+
input={"Company": ["Apple", "Google"]},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
result = riveter.runs.wait_for_result(run.id)
|
|
46
|
+
print(result.output)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## The run lifecycle
|
|
50
|
+
|
|
51
|
+
Every async kickoff (`enrich`, `datasets.build`, `extractions.run`, ...) returns a run.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
riveter.runs.get(run.id) # status + progress
|
|
55
|
+
riveter.runs.result(run.id, wait=50) # output (long-polls up to 50s)
|
|
56
|
+
riveter.runs.wait_for_result(run.id, timeout=600) # poll until finished
|
|
57
|
+
riveter.runs.stop(run.id) # stop early
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
List runs with automatic pagination:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
for run in riveter.runs.list(status="success").auto_paging_iter():
|
|
64
|
+
print(run.id)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Surface
|
|
68
|
+
|
|
69
|
+
- `riveter.enrich(...)`, `riveter.quick_search(...)`, `riveter.scrape(...)`, `riveter.account()`
|
|
70
|
+
- `riveter.runs` — `get`, `result`, `stop`, `list`, `summary`, `wait_for_result`
|
|
71
|
+
- `riveter.enrichments` — `list`, `create`, `get`, `update`, `build_dataset`
|
|
72
|
+
- `riveter.datasets` — `build`, `extend`
|
|
73
|
+
- `riveter.configured_datasets` — `build`
|
|
74
|
+
- `riveter.extractions` — `create`, `get`, `run`
|
|
75
|
+
- `riveter.monitors` — `create`, `list`, `get`, `update`, `runs`
|
|
76
|
+
|
|
77
|
+
Responses are lightweight dataclasses; fields the SDK does not know yet stay reachable
|
|
78
|
+
via `.raw`.
|
|
79
|
+
|
|
80
|
+
## Errors and retries
|
|
81
|
+
|
|
82
|
+
API failures raise `riveter.APIError` with `status`, `type` (`not_found`,
|
|
83
|
+
`insufficient_credits`, ...), `message`, and optional `details`. Network failures raise
|
|
84
|
+
`APIConnectionError` / `APITimeoutError`. 429s are retried automatically using the
|
|
85
|
+
`X-RateLimit-Reset` header; 5xx and network failures are retried for GETs. Configure with
|
|
86
|
+
`max_retries` and `timeout`.
|
|
87
|
+
|
|
88
|
+
## Options
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
Riveter(
|
|
92
|
+
api_key="...", # default: env RIVETER_API_KEY
|
|
93
|
+
base_url="https://api.riveterhq.com/v1", # default: env RIVETER_BASE_URL, then this
|
|
94
|
+
timeout=60.0, # keep above 50 for `wait` long-polls
|
|
95
|
+
max_retries=2,
|
|
96
|
+
)
|
|
97
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# riveter-sdk
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Riveter API](https://docs.riveterhq.com) — enrich data,
|
|
4
|
+
build datasets, scrape pages, and run web searches.
|
|
5
|
+
|
|
6
|
+
Requires Python 3.9+. The import name is `riveter`.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install riveter-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from riveter import Riveter
|
|
18
|
+
|
|
19
|
+
# Reads RIVETER_API_KEY from the environment when api_key is not passed.
|
|
20
|
+
# Get a key at https://app.riveterhq.com/settings/api
|
|
21
|
+
riveter = Riveter(api_key="YOUR_API_KEY")
|
|
22
|
+
|
|
23
|
+
run = riveter.enrich(
|
|
24
|
+
prompt="Research each company",
|
|
25
|
+
attributes=["CEO", "Employee Count"],
|
|
26
|
+
input={"Company": ["Apple", "Google"]},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
result = riveter.runs.wait_for_result(run.id)
|
|
30
|
+
print(result.output)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## The run lifecycle
|
|
34
|
+
|
|
35
|
+
Every async kickoff (`enrich`, `datasets.build`, `extractions.run`, ...) returns a run.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
riveter.runs.get(run.id) # status + progress
|
|
39
|
+
riveter.runs.result(run.id, wait=50) # output (long-polls up to 50s)
|
|
40
|
+
riveter.runs.wait_for_result(run.id, timeout=600) # poll until finished
|
|
41
|
+
riveter.runs.stop(run.id) # stop early
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
List runs with automatic pagination:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
for run in riveter.runs.list(status="success").auto_paging_iter():
|
|
48
|
+
print(run.id)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Surface
|
|
52
|
+
|
|
53
|
+
- `riveter.enrich(...)`, `riveter.quick_search(...)`, `riveter.scrape(...)`, `riveter.account()`
|
|
54
|
+
- `riveter.runs` — `get`, `result`, `stop`, `list`, `summary`, `wait_for_result`
|
|
55
|
+
- `riveter.enrichments` — `list`, `create`, `get`, `update`, `build_dataset`
|
|
56
|
+
- `riveter.datasets` — `build`, `extend`
|
|
57
|
+
- `riveter.configured_datasets` — `build`
|
|
58
|
+
- `riveter.extractions` — `create`, `get`, `run`
|
|
59
|
+
- `riveter.monitors` — `create`, `list`, `get`, `update`, `runs`
|
|
60
|
+
|
|
61
|
+
Responses are lightweight dataclasses; fields the SDK does not know yet stay reachable
|
|
62
|
+
via `.raw`.
|
|
63
|
+
|
|
64
|
+
## Errors and retries
|
|
65
|
+
|
|
66
|
+
API failures raise `riveter.APIError` with `status`, `type` (`not_found`,
|
|
67
|
+
`insufficient_credits`, ...), `message`, and optional `details`. Network failures raise
|
|
68
|
+
`APIConnectionError` / `APITimeoutError`. 429s are retried automatically using the
|
|
69
|
+
`X-RateLimit-Reset` header; 5xx and network failures are retried for GETs. Configure with
|
|
70
|
+
`max_retries` and `timeout`.
|
|
71
|
+
|
|
72
|
+
## Options
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
Riveter(
|
|
76
|
+
api_key="...", # default: env RIVETER_API_KEY
|
|
77
|
+
base_url="https://api.riveterhq.com/v1", # default: env RIVETER_BASE_URL, then this
|
|
78
|
+
timeout=60.0, # keep above 50 for `wait` long-polls
|
|
79
|
+
max_retries=2,
|
|
80
|
+
)
|
|
81
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "riveter-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Riveter API — enrich data, build datasets, scrape pages, and run web searches"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [{ name = "Riveter", email = "support@riveterhq.com" }]
|
|
14
|
+
dependencies = ["httpx>=0.24,<1"]
|
|
15
|
+
keywords = ["riveter", "sdk", "api", "enrichment", "dataset", "scraping", "web-search"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://docs.riveterhq.com"
|
|
19
|
+
Documentation = "https://docs.riveterhq.com"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = ["pytest>=7"]
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["riveter"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.sdist]
|
|
28
|
+
include = ["riveter", "README.md", "LICENSE"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Official Python SDK for the Riveter API. https://docs.riveterhq.com"""
|
|
2
|
+
|
|
3
|
+
from ._client import Riveter
|
|
4
|
+
from ._errors import APIConnectionError, APIError, APITimeoutError, RiveterError
|
|
5
|
+
from ._models import (
|
|
6
|
+
Account,
|
|
7
|
+
AccountInfo,
|
|
8
|
+
AccountUser,
|
|
9
|
+
ApiKeyInfo,
|
|
10
|
+
Credit,
|
|
11
|
+
Enrichment,
|
|
12
|
+
EnrichmentSummary,
|
|
13
|
+
Extraction,
|
|
14
|
+
Monitor,
|
|
15
|
+
Pagination,
|
|
16
|
+
Run,
|
|
17
|
+
RunListItem,
|
|
18
|
+
RunProgress,
|
|
19
|
+
RunsSummary,
|
|
20
|
+
ScrapeResult,
|
|
21
|
+
)
|
|
22
|
+
from ._pagination import RunsPage
|
|
23
|
+
from ._version import __version__
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Riveter",
|
|
27
|
+
"RiveterError",
|
|
28
|
+
"APIError",
|
|
29
|
+
"APIConnectionError",
|
|
30
|
+
"APITimeoutError",
|
|
31
|
+
"Run",
|
|
32
|
+
"RunProgress",
|
|
33
|
+
"RunListItem",
|
|
34
|
+
"RunsPage",
|
|
35
|
+
"RunsSummary",
|
|
36
|
+
"Pagination",
|
|
37
|
+
"Enrichment",
|
|
38
|
+
"EnrichmentSummary",
|
|
39
|
+
"Extraction",
|
|
40
|
+
"Monitor",
|
|
41
|
+
"ScrapeResult",
|
|
42
|
+
"Account",
|
|
43
|
+
"AccountInfo",
|
|
44
|
+
"AccountUser",
|
|
45
|
+
"ApiKeyInfo",
|
|
46
|
+
"Credit",
|
|
47
|
+
"__version__",
|
|
48
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ._errors import APIConnectionError, APIError, APITimeoutError, RiveterError
|
|
10
|
+
from ._models import AccountInfo, Run, ScrapeResult
|
|
11
|
+
from ._resources import ConfiguredDatasets, Datasets, Enrichments, Extractions, Monitors, Runs
|
|
12
|
+
from ._version import __version__
|
|
13
|
+
|
|
14
|
+
DEFAULT_BASE_URL = "https://api.riveterhq.com/v1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _drop_none(mapping: Dict[str, Any]) -> Dict[str, Any]:
|
|
18
|
+
return {key: value for key, value in mapping.items() if value is not None}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Riveter:
|
|
22
|
+
"""Client for the Riveter API. https://docs.riveterhq.com
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
api_key: Falls back to the ``RIVETER_API_KEY`` environment variable.
|
|
26
|
+
base_url: Falls back to ``RIVETER_BASE_URL``, then the production URL.
|
|
27
|
+
timeout: Per-request timeout in seconds; must exceed the 50s ``wait``
|
|
28
|
+
long-poll. Default 60.
|
|
29
|
+
max_retries: Extra attempts after a 429 (any method) or a 5xx/network
|
|
30
|
+
failure (GETs only). Default 2.
|
|
31
|
+
http_client: Custom ``httpx.Client`` (proxies, mock transports, ...).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
api_key: Optional[str] = None,
|
|
37
|
+
base_url: Optional[str] = None,
|
|
38
|
+
timeout: float = 60.0,
|
|
39
|
+
max_retries: int = 2,
|
|
40
|
+
http_client: Optional[httpx.Client] = None,
|
|
41
|
+
):
|
|
42
|
+
self.api_key = api_key or os.environ.get("RIVETER_API_KEY")
|
|
43
|
+
if not self.api_key:
|
|
44
|
+
raise RiveterError(
|
|
45
|
+
"Missing API key: pass api_key or set RIVETER_API_KEY "
|
|
46
|
+
"(get one at https://app.riveterhq.com/settings/api)"
|
|
47
|
+
)
|
|
48
|
+
self.base_url = (base_url or os.environ.get("RIVETER_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
49
|
+
self.timeout = timeout
|
|
50
|
+
self.max_retries = max_retries
|
|
51
|
+
self._http = http_client or httpx.Client(timeout=timeout)
|
|
52
|
+
self.runs = Runs(self)
|
|
53
|
+
self.enrichments = Enrichments(self)
|
|
54
|
+
self.datasets = Datasets(self)
|
|
55
|
+
self.configured_datasets = ConfiguredDatasets(self)
|
|
56
|
+
self.extractions = Extractions(self)
|
|
57
|
+
self.monitors = Monitors(self)
|
|
58
|
+
|
|
59
|
+
def close(self) -> None:
|
|
60
|
+
self._http.close()
|
|
61
|
+
|
|
62
|
+
def __enter__(self) -> "Riveter":
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(self, *exc_info: Any) -> None:
|
|
66
|
+
self.close()
|
|
67
|
+
|
|
68
|
+
# ----- top-level operations -----
|
|
69
|
+
|
|
70
|
+
def enrich(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
enrichment_id: Optional[str] = None,
|
|
74
|
+
output: Optional[Dict[str, Any]] = None,
|
|
75
|
+
prompt: Optional[str] = None,
|
|
76
|
+
attributes: Optional[List[str]] = None,
|
|
77
|
+
input: Optional[Dict[str, List[str]]] = None,
|
|
78
|
+
dataset_id: Optional[str] = None,
|
|
79
|
+
run_key: Optional[str] = None,
|
|
80
|
+
webhook_url: Optional[str] = None,
|
|
81
|
+
allow_duplicate_input: Optional[bool] = None,
|
|
82
|
+
) -> Run:
|
|
83
|
+
"""Start an enrichment run on rows of input data. (operationId: enrich)"""
|
|
84
|
+
body = _drop_none(
|
|
85
|
+
{
|
|
86
|
+
"enrichment_id": enrichment_id,
|
|
87
|
+
"output": output,
|
|
88
|
+
"prompt": prompt,
|
|
89
|
+
"attributes": attributes,
|
|
90
|
+
"input": input,
|
|
91
|
+
"dataset_id": dataset_id,
|
|
92
|
+
"run_key": run_key,
|
|
93
|
+
"webhook_url": webhook_url,
|
|
94
|
+
"allow_duplicate_input": allow_duplicate_input,
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
return Run.from_dict(self.request("POST", "/enrich", json=body))
|
|
98
|
+
|
|
99
|
+
def quick_search(
|
|
100
|
+
self,
|
|
101
|
+
query: str,
|
|
102
|
+
*,
|
|
103
|
+
date_start: Optional[str] = None,
|
|
104
|
+
date_end: Optional[str] = None,
|
|
105
|
+
run_key: Optional[str] = None,
|
|
106
|
+
) -> Run:
|
|
107
|
+
"""Run one web search synchronously; results are already in ``output``.
|
|
108
|
+
(operationId: quickSearch)"""
|
|
109
|
+
body = _drop_none(
|
|
110
|
+
{"query": query, "date_start": date_start, "date_end": date_end, "run_key": run_key}
|
|
111
|
+
)
|
|
112
|
+
return Run.from_dict(self.request("POST", "/quick_search", json=body))
|
|
113
|
+
|
|
114
|
+
def scrape(
|
|
115
|
+
self,
|
|
116
|
+
url: str,
|
|
117
|
+
*,
|
|
118
|
+
proxy_country_code: Optional[str] = None,
|
|
119
|
+
skip_cache: Optional[bool] = None,
|
|
120
|
+
) -> ScrapeResult:
|
|
121
|
+
"""Scrape a webpage and return its text synchronously. (operationId: scrape)"""
|
|
122
|
+
body = _drop_none(
|
|
123
|
+
{"url": url, "proxy_country_code": proxy_country_code, "skip_cache": skip_cache}
|
|
124
|
+
)
|
|
125
|
+
return ScrapeResult.from_dict(self.request("POST", "/scrape", json=body))
|
|
126
|
+
|
|
127
|
+
def account(self) -> AccountInfo:
|
|
128
|
+
"""Account, plan, and credit info for the API key. (operationId: getAccount)"""
|
|
129
|
+
return AccountInfo.from_dict(self.request("GET", "/account"))
|
|
130
|
+
|
|
131
|
+
# ----- transport -----
|
|
132
|
+
|
|
133
|
+
def request(
|
|
134
|
+
self,
|
|
135
|
+
method: str,
|
|
136
|
+
path: str,
|
|
137
|
+
*,
|
|
138
|
+
params: Optional[Dict[str, Any]] = None,
|
|
139
|
+
json: Optional[Dict[str, Any]] = None,
|
|
140
|
+
timeout: Optional[float] = None,
|
|
141
|
+
) -> Any:
|
|
142
|
+
url = self.base_url + path
|
|
143
|
+
headers = {
|
|
144
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
145
|
+
"User-Agent": f"riveter-sdk-python/{__version__}",
|
|
146
|
+
}
|
|
147
|
+
retry_on_failure = method == "GET"
|
|
148
|
+
attempt = 0
|
|
149
|
+
while True:
|
|
150
|
+
try:
|
|
151
|
+
response = self._http.request(
|
|
152
|
+
method,
|
|
153
|
+
url,
|
|
154
|
+
params=_drop_none(params or {}) or None,
|
|
155
|
+
json=json,
|
|
156
|
+
headers=headers,
|
|
157
|
+
timeout=timeout if timeout is not None else self.timeout,
|
|
158
|
+
)
|
|
159
|
+
except httpx.TimeoutException as exc:
|
|
160
|
+
if retry_on_failure and attempt < self.max_retries:
|
|
161
|
+
time.sleep(_backoff_seconds(attempt))
|
|
162
|
+
attempt += 1
|
|
163
|
+
continue
|
|
164
|
+
raise APITimeoutError(f"Request timed out: {method} {path}") from exc
|
|
165
|
+
except httpx.HTTPError as exc:
|
|
166
|
+
if retry_on_failure and attempt < self.max_retries:
|
|
167
|
+
time.sleep(_backoff_seconds(attempt))
|
|
168
|
+
attempt += 1
|
|
169
|
+
continue
|
|
170
|
+
raise APIConnectionError(f"Request failed: {method} {path}: {exc}") from exc
|
|
171
|
+
|
|
172
|
+
if response.is_success:
|
|
173
|
+
return response.json() if response.content else None
|
|
174
|
+
|
|
175
|
+
if response.status_code == 429 and attempt < self.max_retries:
|
|
176
|
+
time.sleep(_rate_limit_delay_seconds(response, attempt))
|
|
177
|
+
attempt += 1
|
|
178
|
+
continue
|
|
179
|
+
if response.status_code >= 500 and retry_on_failure and attempt < self.max_retries:
|
|
180
|
+
time.sleep(_backoff_seconds(attempt))
|
|
181
|
+
attempt += 1
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
raise APIError.from_response(response.status_code, _parse_body(response))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _backoff_seconds(attempt: int) -> float:
|
|
188
|
+
return min(0.5 * (2**attempt), 8.0)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _rate_limit_delay_seconds(response: httpx.Response, attempt: int) -> float:
|
|
192
|
+
try:
|
|
193
|
+
reset = float(response.headers.get("X-RateLimit-Reset", ""))
|
|
194
|
+
except ValueError:
|
|
195
|
+
return _backoff_seconds(attempt)
|
|
196
|
+
if reset <= 0:
|
|
197
|
+
return _backoff_seconds(attempt)
|
|
198
|
+
return min(max(reset - time.time(), 0.0), 30.0)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _parse_body(response: httpx.Response) -> Any:
|
|
202
|
+
try:
|
|
203
|
+
return response.json()
|
|
204
|
+
except ValueError:
|
|
205
|
+
return response.text
|