getanyapi 0.1.0__tar.gz → 0.7.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.
- {getanyapi-0.1.0 → getanyapi-0.7.0}/PKG-INFO +32 -18
- {getanyapi-0.1.0 → getanyapi-0.7.0}/README.md +31 -17
- {getanyapi-0.1.0 → getanyapi-0.7.0}/pyproject.toml +1 -1
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/__init__.py +19 -1
- getanyapi-0.7.0/src/getanyapi/_account.py +144 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/_async_client.py +18 -16
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/_client.py +16 -10
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/__init__.py +5 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/ahrefs.py +33 -32
- getanyapi-0.7.0/src/getanyapi/platforms/airbnb.py +198 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/alibaba.py +49 -10
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/amazon.py +198 -68
- getanyapi-0.7.0/src/getanyapi/platforms/apollo.py +1095 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/appstore.py +34 -8
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/bluesky.py +47 -24
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/booking.py +25 -15
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/coinmarketcap.py +35 -22
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/congress.py +58 -10
- getanyapi-0.7.0/src/getanyapi/platforms/dexscreener.py +187 -0
- getanyapi-0.7.0/src/getanyapi/platforms/douyin.py +549 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/ebay.py +70 -30
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/email.py +18 -16
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/facebook.py +665 -237
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/fiverr.py +14 -10
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/github.py +211 -78
- getanyapi-0.7.0/src/getanyapi/platforms/glassdoor.py +155 -0
- getanyapi-0.7.0/src/getanyapi/platforms/google.py +696 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/google_ads.py +119 -30
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/google_finance.py +15 -11
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/google_shopping.py +62 -8
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/hackernews.py +61 -29
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/indeed.py +20 -10
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/instagram.py +418 -147
- getanyapi-0.7.0/src/getanyapi/platforms/linkedin.py +2818 -0
- getanyapi-0.7.0/src/getanyapi/platforms/maps.py +578 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/pandaexpress.py +45 -23
- getanyapi-0.7.0/src/getanyapi/platforms/person.py +180 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/pinterest.py +6 -4
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/playstore.py +49 -11
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/polymarket.py +52 -8
- getanyapi-0.7.0/src/getanyapi/platforms/realtor.py +214 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/reddit.py +118 -55
- getanyapi-0.7.0/src/getanyapi/platforms/redfin.py +156 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/rednote.py +197 -83
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/sec.py +34 -16
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/semrush.py +20 -16
- getanyapi-0.7.0/src/getanyapi/platforms/seo.py +1293 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/snapchat.py +31 -40
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/social.py +7 -7
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/spotify.py +170 -64
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/substack.py +45 -21
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/threads.py +133 -54
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/tiktok.py +415 -98
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/tiktok_shop.py +98 -34
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/tripadvisor.py +97 -19
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/trustpilot.py +18 -9
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/truthsocial.py +69 -33
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/twitter.py +380 -92
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/upwork.py +49 -21
- getanyapi-0.7.0/src/getanyapi/platforms/walmart.py +146 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/web.py +72 -32
- getanyapi-0.7.0/src/getanyapi/platforms/weibo.py +699 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/whatsapp.py +20 -10
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/yahoo_finance.py +48 -12
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/yelp.py +15 -9
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/platforms/youtube.py +253 -80
- getanyapi-0.7.0/src/getanyapi/platforms/zhihu.py +555 -0
- getanyapi-0.7.0/src/getanyapi/platforms/zillow.py +352 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/types.py +125 -15
- getanyapi-0.7.0/tests/test_account.py +510 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_fixture_sweep.py +14 -12
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_pagination.py +3 -3
- getanyapi-0.1.0/src/getanyapi/_account.py +0 -132
- getanyapi-0.1.0/src/getanyapi/platforms/airbnb.py +0 -120
- getanyapi-0.1.0/src/getanyapi/platforms/dexscreener.py +0 -104
- getanyapi-0.1.0/src/getanyapi/platforms/glassdoor.py +0 -93
- getanyapi-0.1.0/src/getanyapi/platforms/google.py +0 -232
- getanyapi-0.1.0/src/getanyapi/platforms/linkedin.py +0 -1064
- getanyapi-0.1.0/src/getanyapi/platforms/maps.py +0 -412
- getanyapi-0.1.0/src/getanyapi/platforms/person.py +0 -96
- getanyapi-0.1.0/src/getanyapi/platforms/realtor.py +0 -104
- getanyapi-0.1.0/src/getanyapi/platforms/redfin.py +0 -95
- getanyapi-0.1.0/src/getanyapi/platforms/walmart.py +0 -93
- getanyapi-0.1.0/src/getanyapi/platforms/zillow.py +0 -218
- getanyapi-0.1.0/tests/test_account.py +0 -147
- {getanyapi-0.1.0 → getanyapi-0.7.0}/.gitignore +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/_errors.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/_pagination.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/_transport.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/src/getanyapi/py.typed +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/conftest.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_client.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_envelope.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_errors.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_fixture_roundtrip.py +0 -0
- {getanyapi-0.1.0 → getanyapi-0.7.0}/tests/test_transport.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: getanyapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Official typed Python SDK for AnyAPI: any API, one wallet, USD, no subscriptions.
|
|
5
5
|
Project-URL: Homepage, https://getanyapi.com
|
|
6
6
|
Project-URL: Documentation, https://getanyapi.com/docs
|
|
@@ -44,8 +44,9 @@ from getanyapi import AnyAPI
|
|
|
44
44
|
|
|
45
45
|
client = AnyAPI() # reads ANYAPI_API_KEY from the environment
|
|
46
46
|
res = client.reddit.search(query="mechanical keyboard")
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if res.output.found:
|
|
48
|
+
for post in res.output.data.posts:
|
|
49
|
+
print(post.title, post.score)
|
|
49
50
|
print("charged", res.cost_usd, "USD")
|
|
50
51
|
```
|
|
51
52
|
|
|
@@ -82,9 +83,22 @@ except ResultNotFoundError:
|
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
`ResultNotFoundError` subclasses `NotFoundError`, so `except NotFoundError` catches both an
|
|
85
|
-
HTTP 404 and an empty result; catch `ResultNotFoundError` to handle only empty results.
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
HTTP 404 and an empty result; catch `ResultNotFoundError` to handle only empty results. If a
|
|
87
|
+
future committed schema uses a bare output, generated typing returns its data object directly
|
|
88
|
+
rather than relying on a hard-coded SKU list.
|
|
89
|
+
|
|
90
|
+
## Discovery
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
apis = client.catalog(category="search")
|
|
94
|
+
matches = client.search(query="web search", platform="google", limit=10)
|
|
95
|
+
api = client.describe(matches.results[0].slug)
|
|
96
|
+
print(api.pricing.from_offer, api.pricing.failover_max_usd, api.input_schema)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Sync and async clients expose the same category-only `catalog`, dedicated ranked `search`, and
|
|
100
|
+
schema-bearing `describe` methods. Prices are nested USD flat/linear offers, lanes are
|
|
101
|
+
anonymous, and provider is always `"AnyAPI"`.
|
|
88
102
|
|
|
89
103
|
## Pagination
|
|
90
104
|
|
|
@@ -115,17 +129,17 @@ res = client.google.search(
|
|
|
115
129
|
|
|
116
130
|
## Errors and retries
|
|
117
131
|
|
|
118
|
-
| Class
|
|
119
|
-
|
|
|
120
|
-
| `BadRequestError`
|
|
121
|
-
| `AuthenticationError`
|
|
122
|
-
| `InsufficientBalanceError` | 402
|
|
123
|
-
| `NotFoundError`
|
|
124
|
-
| `ResultNotFoundError`
|
|
125
|
-
| `RateLimitedError`
|
|
126
|
-
| `UpstreamError`
|
|
127
|
-
| `ConnectionError`
|
|
128
|
-
| `TimeoutError`
|
|
132
|
+
| Class | HTTP | Meaning |
|
|
133
|
+
| -------------------------- | ---- | ------------------------------------------ |
|
|
134
|
+
| `BadRequestError` | 400 | Input failed validation |
|
|
135
|
+
| `AuthenticationError` | 401 | Missing or invalid API key |
|
|
136
|
+
| `InsufficientBalanceError` | 402 | Wallet balance or per-key cap exceeded |
|
|
137
|
+
| `NotFoundError` | 404 | Slug or resource does not exist |
|
|
138
|
+
| `ResultNotFoundError` | - | `unwrap` on an empty found-data result |
|
|
139
|
+
| `RateLimitedError` | 429 | Too many requests (retried automatically) |
|
|
140
|
+
| `UpstreamError` | 502 | An upstream backend failed |
|
|
141
|
+
| `ConnectionError` | 0 | Network or transport failure (retried) |
|
|
142
|
+
| `TimeoutError` | 0 | Request exceeded its timeout (not retried) |
|
|
129
143
|
|
|
130
144
|
All extend `AnyAPIError` (with `.status` and `.request_id`). Retries cover only 429 and network
|
|
131
145
|
failures, with jittered exponential backoff honoring `Retry-After`. Default `max_retries` is 2
|
|
@@ -143,7 +157,7 @@ result = agent_signup(label="my-agent")
|
|
|
143
157
|
client = AnyAPI(api_key=result.secret)
|
|
144
158
|
```
|
|
145
159
|
|
|
146
|
-
The key ships with a small starter
|
|
160
|
+
The key ships with a small starter balance and a per-key spend cap; a human funds it by claiming
|
|
147
161
|
it at `result.claim_url`.
|
|
148
162
|
|
|
149
163
|
## Docs
|
|
@@ -15,8 +15,9 @@ from getanyapi import AnyAPI
|
|
|
15
15
|
|
|
16
16
|
client = AnyAPI() # reads ANYAPI_API_KEY from the environment
|
|
17
17
|
res = client.reddit.search(query="mechanical keyboard")
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
if res.output.found:
|
|
19
|
+
for post in res.output.data.posts:
|
|
20
|
+
print(post.title, post.score)
|
|
20
21
|
print("charged", res.cost_usd, "USD")
|
|
21
22
|
```
|
|
22
23
|
|
|
@@ -53,9 +54,22 @@ except ResultNotFoundError:
|
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
`ResultNotFoundError` subclasses `NotFoundError`, so `except NotFoundError` catches both an
|
|
56
|
-
HTTP 404 and an empty result; catch `ResultNotFoundError` to handle only empty results.
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
HTTP 404 and an empty result; catch `ResultNotFoundError` to handle only empty results. If a
|
|
58
|
+
future committed schema uses a bare output, generated typing returns its data object directly
|
|
59
|
+
rather than relying on a hard-coded SKU list.
|
|
60
|
+
|
|
61
|
+
## Discovery
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
apis = client.catalog(category="search")
|
|
65
|
+
matches = client.search(query="web search", platform="google", limit=10)
|
|
66
|
+
api = client.describe(matches.results[0].slug)
|
|
67
|
+
print(api.pricing.from_offer, api.pricing.failover_max_usd, api.input_schema)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Sync and async clients expose the same category-only `catalog`, dedicated ranked `search`, and
|
|
71
|
+
schema-bearing `describe` methods. Prices are nested USD flat/linear offers, lanes are
|
|
72
|
+
anonymous, and provider is always `"AnyAPI"`.
|
|
59
73
|
|
|
60
74
|
## Pagination
|
|
61
75
|
|
|
@@ -86,17 +100,17 @@ res = client.google.search(
|
|
|
86
100
|
|
|
87
101
|
## Errors and retries
|
|
88
102
|
|
|
89
|
-
| Class
|
|
90
|
-
|
|
|
91
|
-
| `BadRequestError`
|
|
92
|
-
| `AuthenticationError`
|
|
93
|
-
| `InsufficientBalanceError` | 402
|
|
94
|
-
| `NotFoundError`
|
|
95
|
-
| `ResultNotFoundError`
|
|
96
|
-
| `RateLimitedError`
|
|
97
|
-
| `UpstreamError`
|
|
98
|
-
| `ConnectionError`
|
|
99
|
-
| `TimeoutError`
|
|
103
|
+
| Class | HTTP | Meaning |
|
|
104
|
+
| -------------------------- | ---- | ------------------------------------------ |
|
|
105
|
+
| `BadRequestError` | 400 | Input failed validation |
|
|
106
|
+
| `AuthenticationError` | 401 | Missing or invalid API key |
|
|
107
|
+
| `InsufficientBalanceError` | 402 | Wallet balance or per-key cap exceeded |
|
|
108
|
+
| `NotFoundError` | 404 | Slug or resource does not exist |
|
|
109
|
+
| `ResultNotFoundError` | - | `unwrap` on an empty found-data result |
|
|
110
|
+
| `RateLimitedError` | 429 | Too many requests (retried automatically) |
|
|
111
|
+
| `UpstreamError` | 502 | An upstream backend failed |
|
|
112
|
+
| `ConnectionError` | 0 | Network or transport failure (retried) |
|
|
113
|
+
| `TimeoutError` | 0 | Request exceeded its timeout (not retried) |
|
|
100
114
|
|
|
101
115
|
All extend `AnyAPIError` (with `.status` and `.request_id`). Retries cover only 429 and network
|
|
102
116
|
failures, with jittered exponential backoff honoring `Retry-After`. Default `max_retries` is 2
|
|
@@ -114,7 +128,7 @@ result = agent_signup(label="my-agent")
|
|
|
114
128
|
client = AnyAPI(api_key=result.secret)
|
|
115
129
|
```
|
|
116
130
|
|
|
117
|
-
The key ships with a small starter
|
|
131
|
+
The key ships with a small starter balance and a per-key spend cap; a human funds it by claiming
|
|
118
132
|
it at `result.claim_url`.
|
|
119
133
|
|
|
120
134
|
## Docs
|
|
@@ -32,15 +32,24 @@ from .types import (
|
|
|
32
32
|
Balance,
|
|
33
33
|
BareRunResult,
|
|
34
34
|
CatalogEntry,
|
|
35
|
+
CatalogSearchResult,
|
|
36
|
+
CatalogSearchResults,
|
|
37
|
+
DiscoveryLane,
|
|
38
|
+
DiscoveryPricing,
|
|
39
|
+
FlatPricingOffer,
|
|
40
|
+
HighlightField,
|
|
41
|
+
LaneHealth,
|
|
42
|
+
LinearPricingOffer,
|
|
35
43
|
Output,
|
|
36
44
|
OutputFound,
|
|
37
45
|
OutputNotFound,
|
|
46
|
+
PricingOffer,
|
|
38
47
|
RequestOptions,
|
|
39
48
|
RunResult,
|
|
40
49
|
unwrap,
|
|
41
50
|
)
|
|
42
51
|
|
|
43
|
-
__version__ = "0.
|
|
52
|
+
__version__ = "0.7.0"
|
|
44
53
|
|
|
45
54
|
__all__ = [
|
|
46
55
|
# clients + top-level functions
|
|
@@ -57,6 +66,15 @@ __all__ = [
|
|
|
57
66
|
"Balance",
|
|
58
67
|
"AccountProfile",
|
|
59
68
|
"CatalogEntry",
|
|
69
|
+
"FlatPricingOffer",
|
|
70
|
+
"LinearPricingOffer",
|
|
71
|
+
"PricingOffer",
|
|
72
|
+
"DiscoveryPricing",
|
|
73
|
+
"LaneHealth",
|
|
74
|
+
"DiscoveryLane",
|
|
75
|
+
"HighlightField",
|
|
76
|
+
"CatalogSearchResult",
|
|
77
|
+
"CatalogSearchResults",
|
|
60
78
|
"RequestOptions",
|
|
61
79
|
"AgentSignupResult",
|
|
62
80
|
# pagination
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Account, catalog, and agent-signup request/response mapping (SPEC 2.7, 3.7).
|
|
2
|
+
|
|
3
|
+
Pure functions that build the HTTP request pieces and map raw JSON bodies into
|
|
4
|
+
the public models. The sync and async clients share these so the wire mapping
|
|
5
|
+
lives in one place. Discovery bodies are validated against the customer-safe
|
|
6
|
+
nested USD contract before any public model is returned.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
from ._errors import AnyAPIError, error_for_status
|
|
14
|
+
from ._transport import as_dict, error_message
|
|
15
|
+
from .types import (
|
|
16
|
+
AccountProfile,
|
|
17
|
+
AgentSignupResult,
|
|
18
|
+
Balance,
|
|
19
|
+
CatalogEntry,
|
|
20
|
+
CatalogSearchResults,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"balance_path",
|
|
25
|
+
"me_path",
|
|
26
|
+
"catalog_request",
|
|
27
|
+
"search_request",
|
|
28
|
+
"describe_path",
|
|
29
|
+
"signup_request",
|
|
30
|
+
"parse_balance",
|
|
31
|
+
"parse_me",
|
|
32
|
+
"parse_catalog",
|
|
33
|
+
"parse_describe",
|
|
34
|
+
"parse_search",
|
|
35
|
+
"parse_signup",
|
|
36
|
+
"map_error",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
balance_path = "/v1/balance"
|
|
40
|
+
me_path = "/v1/me"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def catalog_request(category: str | None) -> tuple[str, dict[str, str]]:
|
|
44
|
+
"""Path and query params for ``catalog()`` (GET /v1/apis)."""
|
|
45
|
+
params: dict[str, str] = {}
|
|
46
|
+
if category is not None:
|
|
47
|
+
params["category"] = category
|
|
48
|
+
return "/v1/apis", params
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def search_request(
|
|
52
|
+
query: str,
|
|
53
|
+
category: str | None,
|
|
54
|
+
platform: str | None,
|
|
55
|
+
limit: int | None,
|
|
56
|
+
) -> tuple[str, dict[str, str]]:
|
|
57
|
+
"""Path and query params for dedicated ranked discovery search."""
|
|
58
|
+
params = {"q": query}
|
|
59
|
+
if category is not None:
|
|
60
|
+
params["category"] = category
|
|
61
|
+
if platform is not None:
|
|
62
|
+
params["platform"] = platform
|
|
63
|
+
if limit is not None:
|
|
64
|
+
params["limit"] = str(limit)
|
|
65
|
+
return "/catalog/search", params
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def describe_path(slug: str) -> str:
|
|
69
|
+
return f"/v1/apis/{slug}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def signup_request(sponsor_email: str | None, label: str | None) -> dict[str, Any]:
|
|
73
|
+
"""JSON body for ``agent_signup()`` (POST /agent/signup, no auth)."""
|
|
74
|
+
body: dict[str, Any] = {}
|
|
75
|
+
if sponsor_email is not None:
|
|
76
|
+
body["sponsorEmail"] = sponsor_email
|
|
77
|
+
if label is not None:
|
|
78
|
+
body["label"] = label
|
|
79
|
+
return body
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def parse_balance(body: Any) -> Balance:
|
|
83
|
+
return Balance.model_validate(body)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_me(body: Any) -> AccountProfile:
|
|
87
|
+
"""Map /v1/me, dropping clerkUserId and signupGrantApplied (SPEC 2.7)."""
|
|
88
|
+
return AccountProfile.model_validate(body)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _reject_internal_keys(value: object, path: str) -> None:
|
|
92
|
+
if isinstance(value, list):
|
|
93
|
+
for index, item in enumerate(cast("list[object]", value)):
|
|
94
|
+
_reject_internal_keys(item, f"{path}[{index}]")
|
|
95
|
+
return
|
|
96
|
+
if not isinstance(value, dict):
|
|
97
|
+
return
|
|
98
|
+
for key, item in cast("dict[object, object]", value).items():
|
|
99
|
+
if isinstance(key, str) and "credit" in key.lower():
|
|
100
|
+
raise ValueError(f"malformed discovery response: {path}.{key}")
|
|
101
|
+
_reject_internal_keys(item, f"{path}.{key}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _require_exact_keys(value: dict[str, object], allowed: set[str], path: str) -> None:
|
|
105
|
+
unexpected = set(value) - allowed
|
|
106
|
+
if unexpected:
|
|
107
|
+
key = sorted(unexpected)[0]
|
|
108
|
+
raise ValueError(f"malformed discovery response: {path}.{key}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def parse_catalog(body: object) -> list[CatalogEntry]:
|
|
112
|
+
"""Map {apis:[...]} into CatalogEntry[] (SPEC 2.7)."""
|
|
113
|
+
_reject_internal_keys(body, "catalog")
|
|
114
|
+
envelope = as_dict(body)
|
|
115
|
+
_require_exact_keys(envelope, {"apis"}, "catalog")
|
|
116
|
+
raw = envelope.get("apis")
|
|
117
|
+
if not isinstance(raw, list):
|
|
118
|
+
raise ValueError("malformed discovery response: catalog.apis")
|
|
119
|
+
apis: list[object] = list(cast("list[object]", raw))
|
|
120
|
+
return [CatalogEntry.model_validate(item, strict=True) for item in apis]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def parse_describe(body: object) -> CatalogEntry:
|
|
124
|
+
"""Map a single /v1/apis/{slug} entry into one CatalogEntry."""
|
|
125
|
+
_reject_internal_keys(body, "api")
|
|
126
|
+
entry = CatalogEntry.model_validate(body, strict=True)
|
|
127
|
+
if entry.input_schema is None or entry.output_schema is None:
|
|
128
|
+
raise ValueError("malformed discovery response: detail schemas are required")
|
|
129
|
+
return entry
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def parse_search(body: object) -> CatalogSearchResults:
|
|
133
|
+
"""Validate the dedicated search result envelope."""
|
|
134
|
+
_reject_internal_keys(body, "search")
|
|
135
|
+
return CatalogSearchResults.model_validate(body, strict=True)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def parse_signup(body: Any) -> AgentSignupResult:
|
|
139
|
+
return AgentSignupResult.model_validate(body)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def map_error(status: int, body: object, request_id: str | None) -> AnyAPIError:
|
|
143
|
+
"""Map a non-2xx account/catalog response to the frozen error hierarchy."""
|
|
144
|
+
return error_for_status(status, error_message(body, status), request_id=request_id)
|
|
@@ -30,6 +30,7 @@ from .types import (
|
|
|
30
30
|
AccountProfile,
|
|
31
31
|
Balance,
|
|
32
32
|
CatalogEntry,
|
|
33
|
+
CatalogSearchResults,
|
|
33
34
|
RequestOptions,
|
|
34
35
|
RunResult,
|
|
35
36
|
)
|
|
@@ -42,9 +43,7 @@ _DEFAULT_BASE_URL = "https://api.getanyapi.com"
|
|
|
42
43
|
def _resolve_api_key(api_key: str | None) -> str:
|
|
43
44
|
key = api_key if api_key is not None else os.environ.get("ANYAPI_API_KEY")
|
|
44
45
|
if not key:
|
|
45
|
-
raise AnyAPIError(
|
|
46
|
-
"no API key: pass api_key= or set ANYAPI_API_KEY", status=0
|
|
47
|
-
)
|
|
46
|
+
raise AnyAPIError("no API key: pass api_key= or set ANYAPI_API_KEY", status=0)
|
|
48
47
|
return key
|
|
49
48
|
|
|
50
49
|
|
|
@@ -129,9 +128,7 @@ class AsyncAnyAPI:
|
|
|
129
128
|
continue
|
|
130
129
|
raise
|
|
131
130
|
except httpx.TimeoutException as exc:
|
|
132
|
-
raise TimeoutError(
|
|
133
|
-
str(exc) or "request timed out", status=0
|
|
134
|
-
) from exc
|
|
131
|
+
raise TimeoutError(str(exc) or "request timed out", status=0) from exc
|
|
135
132
|
except httpx.HTTPError as exc:
|
|
136
133
|
if retry.can_retry:
|
|
137
134
|
await asyncio.sleep(retry.next_delay(None))
|
|
@@ -162,17 +159,13 @@ class AsyncAnyAPI:
|
|
|
162
159
|
|
|
163
160
|
# -- account + catalog ------------------------------------------------
|
|
164
161
|
|
|
165
|
-
async def _get(
|
|
166
|
-
self, path: str, params: dict[str, str] | None = None
|
|
167
|
-
) -> object:
|
|
162
|
+
async def _get(self, path: str, params: dict[str, str] | None = None) -> object:
|
|
168
163
|
url = f"{self._base_url}{path}"
|
|
169
164
|
headers = {
|
|
170
165
|
"Authorization": f"Bearer {self._api_key}",
|
|
171
166
|
"Accept": "application/json",
|
|
172
167
|
}
|
|
173
|
-
response = await self._http.get(
|
|
174
|
-
url, params=params or {}, headers=headers
|
|
175
|
-
)
|
|
168
|
+
response = await self._http.get(url, params=params or {}, headers=headers)
|
|
176
169
|
return self._json_or_raise(response)
|
|
177
170
|
|
|
178
171
|
def _json_or_raise(self, response: httpx.Response) -> object:
|
|
@@ -192,12 +185,21 @@ class AsyncAnyAPI:
|
|
|
192
185
|
async def me(self) -> AccountProfile:
|
|
193
186
|
return _account.parse_me(await self._get(_account.me_path))
|
|
194
187
|
|
|
195
|
-
async def catalog(
|
|
196
|
-
|
|
197
|
-
) -> list[CatalogEntry]:
|
|
198
|
-
path, params = _account.catalog_request(query, category)
|
|
188
|
+
async def catalog(self, *, category: str | None = None) -> list[CatalogEntry]:
|
|
189
|
+
path, params = _account.catalog_request(category)
|
|
199
190
|
return _account.parse_catalog(await self._get(path, params))
|
|
200
191
|
|
|
192
|
+
async def search(
|
|
193
|
+
self,
|
|
194
|
+
*,
|
|
195
|
+
query: str,
|
|
196
|
+
category: str | None = None,
|
|
197
|
+
platform: str | None = None,
|
|
198
|
+
limit: int | None = None,
|
|
199
|
+
) -> CatalogSearchResults:
|
|
200
|
+
path, params = _account.search_request(query, category, platform, limit)
|
|
201
|
+
return _account.parse_search(await self._get(path, params))
|
|
202
|
+
|
|
201
203
|
async def describe(self, slug: str) -> CatalogEntry:
|
|
202
204
|
body = await self._get(_account.describe_path(slug))
|
|
203
205
|
return _account.parse_describe(body)
|
|
@@ -49,6 +49,7 @@ from .types import (
|
|
|
49
49
|
AgentSignupResult,
|
|
50
50
|
Balance,
|
|
51
51
|
CatalogEntry,
|
|
52
|
+
CatalogSearchResults,
|
|
52
53
|
RequestOptions,
|
|
53
54
|
RunResult,
|
|
54
55
|
)
|
|
@@ -71,9 +72,7 @@ def lookup_namespace(name: str) -> tuple[str, str, str] | None:
|
|
|
71
72
|
def _resolve_api_key(api_key: str | None) -> str:
|
|
72
73
|
key = api_key if api_key is not None else os.environ.get("ANYAPI_API_KEY")
|
|
73
74
|
if not key:
|
|
74
|
-
raise AnyAPIError(
|
|
75
|
-
"no API key: pass api_key= or set ANYAPI_API_KEY", status=0
|
|
76
|
-
)
|
|
75
|
+
raise AnyAPIError("no API key: pass api_key= or set ANYAPI_API_KEY", status=0)
|
|
77
76
|
return key
|
|
78
77
|
|
|
79
78
|
|
|
@@ -164,9 +163,7 @@ class AnyAPI:
|
|
|
164
163
|
continue
|
|
165
164
|
raise
|
|
166
165
|
except httpx.TimeoutException as exc:
|
|
167
|
-
raise TimeoutError(
|
|
168
|
-
str(exc) or "request timed out", status=0
|
|
169
|
-
) from exc
|
|
166
|
+
raise TimeoutError(str(exc) or "request timed out", status=0) from exc
|
|
170
167
|
except httpx.HTTPError as exc:
|
|
171
168
|
if retry.can_retry:
|
|
172
169
|
_transport.sleep(retry.next_delay(None))
|
|
@@ -223,12 +220,21 @@ class AnyAPI:
|
|
|
223
220
|
def me(self) -> AccountProfile:
|
|
224
221
|
return _account.parse_me(self._get(_account.me_path))
|
|
225
222
|
|
|
226
|
-
def catalog(
|
|
227
|
-
|
|
228
|
-
) -> list[CatalogEntry]:
|
|
229
|
-
path, params = _account.catalog_request(query, category)
|
|
223
|
+
def catalog(self, *, category: str | None = None) -> list[CatalogEntry]:
|
|
224
|
+
path, params = _account.catalog_request(category)
|
|
230
225
|
return _account.parse_catalog(self._get(path, params))
|
|
231
226
|
|
|
227
|
+
def search(
|
|
228
|
+
self,
|
|
229
|
+
*,
|
|
230
|
+
query: str,
|
|
231
|
+
category: str | None = None,
|
|
232
|
+
platform: str | None = None,
|
|
233
|
+
limit: int | None = None,
|
|
234
|
+
) -> CatalogSearchResults:
|
|
235
|
+
path, params = _account.search_request(query, category, platform, limit)
|
|
236
|
+
return _account.parse_search(self._get(path, params))
|
|
237
|
+
|
|
232
238
|
def describe(self, slug: str) -> CatalogEntry:
|
|
233
239
|
return _account.parse_describe(self._get(_account.describe_path(slug)))
|
|
234
240
|
|
|
@@ -13,6 +13,7 @@ REGISTRY: dict[str, tuple[str, str, str]] = {
|
|
|
13
13
|
"airbnb": ("airbnb", "AirbnbNamespace", "AsyncAirbnbNamespace"),
|
|
14
14
|
"alibaba": ("alibaba", "AlibabaNamespace", "AsyncAlibabaNamespace"),
|
|
15
15
|
"amazon": ("amazon", "AmazonNamespace", "AsyncAmazonNamespace"),
|
|
16
|
+
"apollo": ("apollo", "ApolloNamespace", "AsyncApolloNamespace"),
|
|
16
17
|
"appstore": ("appstore", "AppstoreNamespace", "AsyncAppstoreNamespace"),
|
|
17
18
|
"bluesky": ("bluesky", "BlueskyNamespace", "AsyncBlueskyNamespace"),
|
|
18
19
|
"booking": ("booking", "BookingNamespace", "AsyncBookingNamespace"),
|
|
@@ -23,6 +24,7 @@ REGISTRY: dict[str, tuple[str, str, str]] = {
|
|
|
23
24
|
),
|
|
24
25
|
"congress": ("congress", "CongressNamespace", "AsyncCongressNamespace"),
|
|
25
26
|
"dexscreener": ("dexscreener", "DexscreenerNamespace", "AsyncDexscreenerNamespace"),
|
|
27
|
+
"douyin": ("douyin", "DouyinNamespace", "AsyncDouyinNamespace"),
|
|
26
28
|
"ebay": ("ebay", "EbayNamespace", "AsyncEbayNamespace"),
|
|
27
29
|
"email": ("email", "EmailNamespace", "AsyncEmailNamespace"),
|
|
28
30
|
"facebook": ("facebook", "FacebookNamespace", "AsyncFacebookNamespace"),
|
|
@@ -61,6 +63,7 @@ REGISTRY: dict[str, tuple[str, str, str]] = {
|
|
|
61
63
|
"rednote": ("rednote", "RednoteNamespace", "AsyncRednoteNamespace"),
|
|
62
64
|
"sec": ("sec", "SecNamespace", "AsyncSecNamespace"),
|
|
63
65
|
"semrush": ("semrush", "SemrushNamespace", "AsyncSemrushNamespace"),
|
|
66
|
+
"seo": ("seo", "SeoNamespace", "AsyncSeoNamespace"),
|
|
64
67
|
"snapchat": ("snapchat", "SnapchatNamespace", "AsyncSnapchatNamespace"),
|
|
65
68
|
"social": ("social", "SocialNamespace", "AsyncSocialNamespace"),
|
|
66
69
|
"spotify": ("spotify", "SpotifyNamespace", "AsyncSpotifyNamespace"),
|
|
@@ -75,6 +78,7 @@ REGISTRY: dict[str, tuple[str, str, str]] = {
|
|
|
75
78
|
"upwork": ("upwork", "UpworkNamespace", "AsyncUpworkNamespace"),
|
|
76
79
|
"walmart": ("walmart", "WalmartNamespace", "AsyncWalmartNamespace"),
|
|
77
80
|
"web": ("web", "WebNamespace", "AsyncWebNamespace"),
|
|
81
|
+
"weibo": ("weibo", "WeiboNamespace", "AsyncWeiboNamespace"),
|
|
78
82
|
"whatsapp": ("whatsapp", "WhatsappNamespace", "AsyncWhatsappNamespace"),
|
|
79
83
|
"yahoo_finance": (
|
|
80
84
|
"yahoo_finance",
|
|
@@ -83,6 +87,7 @@ REGISTRY: dict[str, tuple[str, str, str]] = {
|
|
|
83
87
|
),
|
|
84
88
|
"yelp": ("yelp", "YelpNamespace", "AsyncYelpNamespace"),
|
|
85
89
|
"youtube": ("youtube", "YoutubeNamespace", "AsyncYoutubeNamespace"),
|
|
90
|
+
"zhihu": ("zhihu", "ZhihuNamespace", "AsyncZhihuNamespace"),
|
|
86
91
|
"zillow": ("zillow", "ZillowNamespace", "AsyncZillowNamespace"),
|
|
87
92
|
}
|
|
88
93
|
|