renidly 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.
- renidly-0.1.0/.github/workflows/ci.yml +28 -0
- renidly-0.1.0/.github/workflows/release.yml +20 -0
- renidly-0.1.0/.gitignore +12 -0
- renidly-0.1.0/PKG-INFO +429 -0
- renidly-0.1.0/README.md +406 -0
- renidly-0.1.0/examples/quickstart.py +181 -0
- renidly-0.1.0/pyproject.toml +46 -0
- renidly-0.1.0/src/renidly/__init__.py +51 -0
- renidly-0.1.0/src/renidly/_batch.py +177 -0
- renidly-0.1.0/src/renidly/_client.py +122 -0
- renidly-0.1.0/src/renidly/_config.py +86 -0
- renidly-0.1.0/src/renidly/_errors.py +105 -0
- renidly-0.1.0/src/renidly/_models.py +73 -0
- renidly-0.1.0/src/renidly/_pagination.py +113 -0
- renidly-0.1.0/src/renidly/_ratelimit.py +167 -0
- renidly-0.1.0/src/renidly/_transport.py +217 -0
- renidly-0.1.0/src/renidly/_version.py +1 -0
- renidly-0.1.0/src/renidly/py.typed +0 -0
- renidly-0.1.0/src/renidly/resources/__init__.py +3 -0
- renidly-0.1.0/src/renidly/resources/_base.py +114 -0
- renidly-0.1.0/src/renidly/resources/_paginators.py +28 -0
- renidly-0.1.0/src/renidly/resources/account.py +131 -0
- renidly-0.1.0/src/renidly/resources/data.py +352 -0
- renidly-0.1.0/src/renidly/resources/emails.py +179 -0
- renidly-0.1.0/src/renidly/resources/live.py +515 -0
- renidly-0.1.0/src/renidly/types/__init__.py +1 -0
- renidly-0.1.0/src/renidly/types/params.py +283 -0
- renidly-0.1.0/tests/test_core.py +167 -0
- renidly-0.1.0/tests/test_ratelimit.py +39 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
cache: pip
|
|
21
|
+
- run: python -m pip install --upgrade pip
|
|
22
|
+
- run: pip install -e ".[dev]"
|
|
23
|
+
- name: Lint
|
|
24
|
+
run: ruff check src tests
|
|
25
|
+
- name: Type-check
|
|
26
|
+
run: mypy src/renidly
|
|
27
|
+
- name: Test
|
|
28
|
+
run: pytest -q
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # PyPI Trusted Publishing (OIDC) — no token needed
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: python -m pip install --upgrade build
|
|
19
|
+
- run: python -m build
|
|
20
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
renidly-0.1.0/.gitignore
ADDED
renidly-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: renidly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Python SDK for the Renidly B2B professional data APIs (Data, Live, Email, Account).
|
|
5
|
+
Project-URL: Homepage, https://renidly.com
|
|
6
|
+
Project-URL: Source, https://github.com/renidly/renidly-python
|
|
7
|
+
Author: Renidly
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: b2b,email verification,enrichment,professional data,renidly,sdk
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Requires-Dist: pydantic>=2.6
|
|
13
|
+
Requires-Dist: typing-extensions>=4.7
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: build>=1; extra == 'dev'
|
|
16
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
19
|
+
Requires-Dist: pyyaml>=6; extra == 'dev'
|
|
20
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Renidly Python SDK
|
|
25
|
+
|
|
26
|
+
The official Python SDK for the [Renidly](https://renidly.com) B2B professional data APIs — resolve, search, enrich, and verify professional identities (people, organizations, institutions, skills, professional activity, job opportunities, and business email) through one clean, typed client.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from renidly import Renidly
|
|
30
|
+
|
|
31
|
+
renidly = Renidly("rnd-...")
|
|
32
|
+
|
|
33
|
+
person = renidly.data.people.retrieve(handle="ryanroslansky")
|
|
34
|
+
company = renidly.data.companies.retrieve(slug="stripe")
|
|
35
|
+
email = renidly.emails.verify("sundar@google.com")
|
|
36
|
+
|
|
37
|
+
print(person.headline, company.name, email.deliverable)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **One client, four products** — `data`, `live`, `emails`, `account`, all off the same key.
|
|
41
|
+
- **Fully typed** — method names, parameters, and returns autocomplete in your IDE (ships `py.typed`).
|
|
42
|
+
- **Batteries included** — automatic retries, transparent pagination, batch jobs, a typed error hierarchy, and an optional self-tuning rate limiter.
|
|
43
|
+
- **Sync and async** — identical surface; just `await`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Table of contents
|
|
48
|
+
|
|
49
|
+
- [Install](#install)
|
|
50
|
+
- [Quickstart](#quickstart)
|
|
51
|
+
- [Authentication](#authentication)
|
|
52
|
+
- [The four products](#the-four-products)
|
|
53
|
+
- [Pagination](#pagination)
|
|
54
|
+
- [Batch jobs](#batch-jobs)
|
|
55
|
+
- [Errors](#errors)
|
|
56
|
+
- [Configuration](#configuration)
|
|
57
|
+
- [Automatic rate limiting](#automatic-rate-limiting)
|
|
58
|
+
- [Async](#async)
|
|
59
|
+
- [Response objects](#response-objects)
|
|
60
|
+
- [Advanced](#advanced)
|
|
61
|
+
- [Requirements & support](#requirements--support)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pip install renidly
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires **Python 3.9+**.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Quickstart
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from renidly import Renidly
|
|
79
|
+
|
|
80
|
+
renidly = Renidly("rnd-...") # or Renidly() and set RENIDLY_API_KEY
|
|
81
|
+
|
|
82
|
+
# Retrieve a single record (returns None if nothing resolved)
|
|
83
|
+
person = renidly.data.people.retrieve(id="prsn_...")
|
|
84
|
+
if person:
|
|
85
|
+
print(person.first_name, person.headline)
|
|
86
|
+
|
|
87
|
+
# Search with any filters — they autocomplete in your editor
|
|
88
|
+
for p in renidly.data.people.search(title="cto", current_only=True).auto_paging_iter():
|
|
89
|
+
print(p.headline)
|
|
90
|
+
|
|
91
|
+
# Verify an email
|
|
92
|
+
v = renidly.emails.verify("sundar@google.com")
|
|
93
|
+
print(v.deliverable, v.reason)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That's the whole feel: `renidly.<product>.<resource>.<action>(...)`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Authentication
|
|
101
|
+
|
|
102
|
+
Pass your key positionally, via config, or through the environment — whichever you prefer.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
Renidly("rnd-...") # positional
|
|
106
|
+
Renidly() # reads RENIDLY_API_KEY
|
|
107
|
+
Renidly(config=RenidlyConfig(api_key="rnd-...")) # inside the config object
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Per-request override (e.g. multi-tenant apps):
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
renidly.data.people.search(title="cto", options={"api_key": "rnd-tenant-key"})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Grab your key from [Workspace → API Keys](https://renidly.com/dashboard/key).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## The four products
|
|
121
|
+
|
|
122
|
+
### `data` — clean, queryable records
|
|
123
|
+
|
|
124
|
+
Deduplicated professional records addressable by stable opaque IDs (`prsn_`, `org_`, `inst_`, `skl_`) or rich filters.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# People
|
|
128
|
+
renidly.data.people.retrieve(id="prsn_...") # or handle="..."
|
|
129
|
+
renidly.data.people.search(title="cto", skills="python", geo_country_code="US")
|
|
130
|
+
renidly.data.people.enrich_batch(handles=[...], ids=[...], live=True) # bulk (see Batch jobs)
|
|
131
|
+
|
|
132
|
+
# Companies
|
|
133
|
+
renidly.data.companies.retrieve(slug="google") # or id="org_..."
|
|
134
|
+
renidly.data.companies.search(name="stripe", staff_count_min=100)
|
|
135
|
+
renidly.data.companies.employees("google", title="engineer", current_only=True)
|
|
136
|
+
renidly.data.companies.enrich_batch(ids=[...]) # bulk (see Batch jobs)
|
|
137
|
+
|
|
138
|
+
# Institutions
|
|
139
|
+
renidly.data.institutions.retrieve("stanford") # by normalized name
|
|
140
|
+
renidly.data.institutions.search("stanford")
|
|
141
|
+
renidly.data.institutions.alumni("stanford", degree="MBA")
|
|
142
|
+
|
|
143
|
+
# Skills
|
|
144
|
+
renidly.data.skills.retrieve("skl_...")
|
|
145
|
+
renidly.data.skills.search("python")
|
|
146
|
+
|
|
147
|
+
# Job changes — trigger-based prospecting
|
|
148
|
+
renidly.data.job_changes.search(event_type="joined", days_ago=30)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `live` — freshest snapshot on demand
|
|
152
|
+
|
|
153
|
+
Resolve a single subject or run a discovery search.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# People — resolve a public handle to a stable id once, then reuse it
|
|
157
|
+
eid = renidly.live.people.resolve_handle("williamhgates").entityId
|
|
158
|
+
renidly.live.people.enrich(eid) # or handle="..."
|
|
159
|
+
renidly.live.people.employment_history(eid)
|
|
160
|
+
renidly.live.people.endorsements(eid)
|
|
161
|
+
renidly.live.people.lookalikes(eid)
|
|
162
|
+
renidly.live.people.interests(eid)
|
|
163
|
+
|
|
164
|
+
# Organizations
|
|
165
|
+
oid = renidly.live.organizations.resolve_slug("google").id
|
|
166
|
+
renidly.live.organizations.enrich(oid)
|
|
167
|
+
renidly.live.organizations.headcount(oid)
|
|
168
|
+
renidly.live.organizations.similar(oid)
|
|
169
|
+
renidly.live.organizations.affiliated(oid)
|
|
170
|
+
renidly.live.organizations.activities(oid)
|
|
171
|
+
renidly.live.organizations.opportunities("1441,1035") # comma-separated org ids
|
|
172
|
+
|
|
173
|
+
# Opportunities (job postings)
|
|
174
|
+
renidly.live.opportunities.retrieve("4019200001")
|
|
175
|
+
renidly.live.opportunities.similar("4019200001")
|
|
176
|
+
renidly.live.opportunities.related_views("4019200001")
|
|
177
|
+
renidly.live.opportunities.hiring_team("4019200001")
|
|
178
|
+
renidly.live.opportunities.by_person(eid)
|
|
179
|
+
|
|
180
|
+
# Activity
|
|
181
|
+
renidly.live.activities.feed(eid)
|
|
182
|
+
renidly.live.activities.retrieve(activity_id)
|
|
183
|
+
renidly.live.activities.reactions(activity_id)
|
|
184
|
+
renidly.live.activities.replies(activity_id, sort_by="date_posted")
|
|
185
|
+
renidly.live.activities.replies_by_author(eid)
|
|
186
|
+
|
|
187
|
+
# Discover (search)
|
|
188
|
+
renidly.live.discover.people(keyword="cto", count=25)
|
|
189
|
+
renidly.live.discover.organizations(keyword="fintech", headcountRange="51-200")
|
|
190
|
+
renidly.live.discover.opportunities(keyword="python", workplaceTypes="remote")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `emails` — verify, find, and resolve
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
renidly.emails.verify("sundar@google.com")
|
|
197
|
+
renidly.emails.find(first_name="Patrick", last_name="Collison", domain="stripe.com")
|
|
198
|
+
renidly.emails.find_by_url("https://example.com/in/someone") # from a professional profile URL
|
|
199
|
+
renidly.emails.reverse("john@acme.com") # who is behind this business email
|
|
200
|
+
renidly.emails.prospects("acme.com", kind="verified_only") # known contacts for a domain
|
|
201
|
+
|
|
202
|
+
# Bulk (see Batch jobs)
|
|
203
|
+
renidly.emails.verify_batch(["a@x.com", "b@y.com"])
|
|
204
|
+
renidly.emails.find_batch([{"first_name": "A", "last_name": "B", "domain": "acme.com"}])
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `account` — balance, tier, and pricing
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
renidly.account.balance().balance
|
|
211
|
+
renidly.account.tier().current_tier.limit_per_minute
|
|
212
|
+
renidly.account.enterprise_balance() # for an Enterprise workspace
|
|
213
|
+
renidly.account.tiers() # public tier ladder (no key needed)
|
|
214
|
+
renidly.account.route_costs() # per-endpoint credit costs (no key needed)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Pagination
|
|
220
|
+
|
|
221
|
+
Every `search`/list method returns a list you can use directly **and** page through transparently.
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
page = renidly.data.people.search(title="cto", limit=25)
|
|
225
|
+
|
|
226
|
+
len(page) # items on this page
|
|
227
|
+
page[0] # index it
|
|
228
|
+
for p in page: ... # iterate this page
|
|
229
|
+
page.has_more # is there more?
|
|
230
|
+
|
|
231
|
+
# ...or walk EVERY page lazily (fetches as it goes, one page in memory at a time)
|
|
232
|
+
for person in renidly.data.people.search(title="cto").auto_paging_iter():
|
|
233
|
+
print(person.headline)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Batch jobs
|
|
239
|
+
|
|
240
|
+
Process up to 1000 items in one async job. Submit returns a handle instantly.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
job = renidly.data.people.enrich_batch(handles=["ryanroslansky", "williamhgates"], live=True)
|
|
244
|
+
|
|
245
|
+
# block until done and collect everything
|
|
246
|
+
result = job.wait(on_progress=lambda n: print("resolved", n))
|
|
247
|
+
print(result.status, result.resolved, "/", result.total)
|
|
248
|
+
for row in result.results:
|
|
249
|
+
print(row.matched_input, "->", row.headline)
|
|
250
|
+
print("not found:", result.not_found)
|
|
251
|
+
|
|
252
|
+
# ...or stream results as they resolve
|
|
253
|
+
for row in renidly.emails.verify_batch(["a@x.com", "b@y.com"]).stream():
|
|
254
|
+
print(row.email, row.deliverable)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Available on `data.people.enrich_batch`, `data.companies.enrich_batch`, `emails.verify_batch`, `emails.find_batch`.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Errors
|
|
262
|
+
|
|
263
|
+
Every failure raises a specific subclass of `RenidlyError`, and the message tells you exactly what went wrong.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from renidly import (
|
|
267
|
+
RenidlyError, AuthenticationError, InvalidRequestError,
|
|
268
|
+
InsufficientCreditsError, NotFoundError, RateLimitError,
|
|
269
|
+
PermissionDeniedError, ServiceUnavailableError,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
renidly.emails.find(first_name="A", last_name="B", domain="bad")
|
|
274
|
+
except InvalidRequestError as e:
|
|
275
|
+
print(e.message) # "Validation failed"
|
|
276
|
+
print(e.field_errors) # {"domain": "must be a bare hostname"}
|
|
277
|
+
except RateLimitError as e:
|
|
278
|
+
print(e.tier, e.limit, e.retry_after)
|
|
279
|
+
except InsufficientCreditsError:
|
|
280
|
+
...
|
|
281
|
+
except RenidlyError as e: # catch-all
|
|
282
|
+
print(e.status_code, e.error_code, e.message, e.errors)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The string form includes the detail, so an uncaught error is self-explanatory:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
InvalidRequestError: Validation failed — domain: must be a bare hostname (VALIDATION_ERROR, HTTP 400)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
| Exception | When |
|
|
292
|
+
|---|---|
|
|
293
|
+
| `AuthenticationError` | missing / invalid key |
|
|
294
|
+
| `PermissionDeniedError` | key valid but not allowed here |
|
|
295
|
+
| `InvalidRequestError` | bad input (see `.field_errors`) |
|
|
296
|
+
| `InsufficientCreditsError` | not enough credits |
|
|
297
|
+
| `NotFoundError` | job not found / expired |
|
|
298
|
+
| `RateLimitError` | per-minute limit hit (`.tier`, `.limit`, `.retry_after`) |
|
|
299
|
+
| `ServiceUnavailableError` | temporary — retry shortly |
|
|
300
|
+
| `APIConnectionError` | network / timeout |
|
|
301
|
+
|
|
302
|
+
> **Not-found lookups:** a single `retrieve(...)` that resolves nothing returns `None` by default (not an exception). Set `raise_on_not_found=True` to raise instead.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Configuration
|
|
307
|
+
|
|
308
|
+
All options live on one object, `RenidlyConfig`:
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from renidly import Renidly, RenidlyConfig
|
|
312
|
+
|
|
313
|
+
renidly = Renidly("rnd-...", config=RenidlyConfig(
|
|
314
|
+
timeout=30,
|
|
315
|
+
max_retries=3, # auto-retry on 429 / 503 / connection errors (backoff + jitter)
|
|
316
|
+
unwrap_data_obj=True, # return the data model (False -> the full envelope)
|
|
317
|
+
raise_on_not_found=False, # single lookups return None when empty (True -> raise)
|
|
318
|
+
raise_on_api_error=True, # map failures to typed exceptions
|
|
319
|
+
auto_rate_limit=False, # see below
|
|
320
|
+
))
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
| Option | Default | Meaning |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| `api_key` | `RENIDLY_API_KEY` env | Your key (positional arg overrides this). |
|
|
326
|
+
| `timeout` | `30` | Per-request timeout (seconds). |
|
|
327
|
+
| `max_retries` | `2` | Retries on transient failures. |
|
|
328
|
+
| `backoff_factor` | `0.5` | Base seconds for exponential backoff. |
|
|
329
|
+
| `base_url` | `https://renidly.com` | Override the API host. |
|
|
330
|
+
| `proxy` | `None` | HTTP(S) proxy URL. |
|
|
331
|
+
| `default_headers` | `{}` | Extra headers on every request. |
|
|
332
|
+
| `unwrap_data_obj` | `True` | Return `data` vs the full envelope. |
|
|
333
|
+
| `raise_on_not_found` | `False` | `None` vs `NotFoundError` on empty lookups. |
|
|
334
|
+
| `raise_on_api_error` | `True` | Raise vs return `None` on API errors. |
|
|
335
|
+
| `auto_rate_limit` | `False` | Self-throttle to your tier's limit. |
|
|
336
|
+
| `rate_limit_per_minute` | `None` | Fixed limit (required for enterprise keys). |
|
|
337
|
+
| `rate_limit_safety` | `1.0` | Fraction of the limit to target (e.g. `0.9`). |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Automatic rate limiting
|
|
342
|
+
|
|
343
|
+
Turn it on and the SDK keeps you under your per-minute limit automatically — no limiter to build.
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
# Regular key: the limit is read from your tier and refreshed automatically.
|
|
347
|
+
Renidly("rnd-...", config=RenidlyConfig(auto_rate_limit=True))
|
|
348
|
+
|
|
349
|
+
# Enterprise key: the limit is fixed — supply it.
|
|
350
|
+
Renidly("enterprise-...", config=RenidlyConfig(auto_rate_limit=True, rate_limit_per_minute=550))
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
It uses a sliding 60-second window so you never exceed the limit, and re-reads your tier after a `429`.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Async
|
|
358
|
+
|
|
359
|
+
Same surface, same names — just `await` (and use it as a context manager to auto-close the connection pool).
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
import asyncio
|
|
363
|
+
from renidly import AsyncRenidly
|
|
364
|
+
|
|
365
|
+
async def main():
|
|
366
|
+
async with AsyncRenidly("rnd-...") as renidly:
|
|
367
|
+
company = await renidly.data.companies.retrieve(slug="stripe")
|
|
368
|
+
print(company.name)
|
|
369
|
+
|
|
370
|
+
async for p in renidly.data.people.search(title="cto").auto_paging_iter():
|
|
371
|
+
print(p.headline)
|
|
372
|
+
|
|
373
|
+
asyncio.run(main())
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Response objects
|
|
379
|
+
|
|
380
|
+
Responses are dynamic and drill-able — access any field (nested included) as an attribute, no schema classes required.
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
t = renidly.account.tier()
|
|
384
|
+
t.current_tier.name # nested attribute access, arbitrarily deep
|
|
385
|
+
t.model_dump() # convert to a plain dict anytime
|
|
386
|
+
|
|
387
|
+
# HTTP metadata is attached to every object
|
|
388
|
+
t.last_response.status_code
|
|
389
|
+
t.last_response.request_id
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Prefer the raw envelope? Set `unwrap_data_obj=False` and every call returns `APIResponse(success=..., data=..., message=...)`.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Advanced
|
|
397
|
+
|
|
398
|
+
**Per-request options** override config for a single call:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
renidly.data.skills.search("python", options={"timeout": 5, "api_key": "rnd-other"})
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Escape hatch** — call any endpoint directly:
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
env = renidly.raw_request("GET", "/people/search", service="data", params={"title": "cto"})
|
|
408
|
+
print(env.success, env.data)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Bring your own HTTP client** (connection pools, custom timeouts, mounts):
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
import httpx
|
|
415
|
+
Renidly("rnd-...", http_client=httpx.Client(limits=httpx.Limits(max_connections=50)))
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Requirements & support
|
|
421
|
+
|
|
422
|
+
- Python **3.9+**
|
|
423
|
+
- Depends only on [`httpx`](https://www.python-httpx.org/) and [`pydantic`](https://docs.pydantic.dev/) v2.
|
|
424
|
+
|
|
425
|
+
Questions or issues? Open one on [GitHub](https://github.com/renidly/renidly-python/issues).
|
|
426
|
+
|
|
427
|
+
## License
|
|
428
|
+
|
|
429
|
+
MIT
|