oneshot-python 0.38.0__tar.gz → 0.40.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.
- oneshot_python-0.40.0/CHANGELOG.md +7 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/PKG-INFO +1 -1
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/README.md +16 -1
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/client.py +98 -72
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/lookup_manifest.py +318 -131
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/pyproject.toml +1 -1
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_declared_routes.py +4 -4
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_lookup.py +8 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_paid_call_proof.py +1 -1
- oneshot_python-0.40.0/tests/test_person_quote_credits.py +130 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_phones_pending.py +13 -1
- oneshot_python-0.40.0/tests/test_product_find.py +18 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/.gitignore +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/__init__.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/_errors.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/_types.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/lookup.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/physical_mail.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/oneshot/x402.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/__init__.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/conftest.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_access_token.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_approvals.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_balance.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_browser.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_budgets.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_charge_amount.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_compute.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_compute_manifest.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_compute_outputs.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_declared_worker_reads.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_domains.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_domains_signed.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_email_payload.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_emergency_error.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_hunts.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_inbound_voice.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_inbox_notifications.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_linkedin.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_max_cost_header.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_payment_rejection.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_physical_mail.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_poll_backoff.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_request_id.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_signed_job_and_analytics.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_tag_receipt_value.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_top_up_credits.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_update_build.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_wait_false.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/tests/test_x402.py +0 -0
- {oneshot_python-0.38.0 → oneshot_python-0.40.0}/uv.lock +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.40.0 — breaking release (#1336)
|
|
4
|
+
|
|
5
|
+
- Renamed `commerce_search()` / `acommerce_search()` → `product_find()` / `aproduct_find()`, without aliases.
|
|
6
|
+
- Product discovery now calls `POST /v1/tools/find/product` (price key `find/product`), replacing `POST /v1/tools/commerce/search` (`commerce/search`). Input, results and Purchase compatibility are unchanged.
|
|
7
|
+
- Release together with the person/company and other tool renames in #1334 and #1382; publish only with the coordinated server release.
|
|
@@ -244,7 +244,7 @@ except LinkedInConnectRequiredError:
|
|
|
244
244
|
|
|
245
245
|
Account reads stop offering reconnect after confirming upstream deletion.
|
|
246
246
|
Missing accounts are excluded from the active list; `include_revoked=True`
|
|
247
|
-
lets you inspect their `
|
|
247
|
+
lets you inspect their `connection_deleted` status and `reconnect_required=False`.
|
|
248
248
|
Authentication failures and timeouts remain ordinary errors and do not instruct
|
|
249
249
|
the consumer to create a new connection.
|
|
250
250
|
|
|
@@ -343,3 +343,18 @@ async form).
|
|
|
343
343
|
## License
|
|
344
344
|
|
|
345
345
|
MIT
|
|
346
|
+
|
|
347
|
+
## Optional phone lookup
|
|
348
|
+
|
|
349
|
+
Phone lookup is off by default. Source-returned numbers and published business or record numbers still appear. `include_phone=True` adds $0.10 × the requested search limit (default 100 → $10), or $0.10 for profile/dossier, for up to that many people with no guaranteed hit. Fallbacks and callbacks cost nothing extra. A completed lookup with no phone match is charged; a failed job is refunded.
|
|
350
|
+
|
|
351
|
+
`max_cost` and per-transaction budgets apply to the full quote before authorization, even when credits cover it. Wallet and access-token sessions both obtain a quote before execution and retry with `x-quote-id`.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
people = client.person_find(job_titles=["CTO"], limit=3, include_phone=True, max_cost=1, wait_for_phones=True)
|
|
355
|
+
profile = client.person_profile(email="jane@example.com", include_phone=True, max_cost=1, wait_for_phones=True)
|
|
356
|
+
dossier = client.person_dossier(email="jane@example.com", include_phone=True, max_cost=1, wait_for_phones=True, timeout_sec=600)
|
|
357
|
+
# Async equivalents: await client.aperson_find(...), aperson_profile(...), aperson_dossier(...).
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`wait=False` returns the job and receipt IDs immediately. `wait_for_phones=True` keeps polling for pending phone callbacks after completion; it does not buy phones by itself. On timeout, the latest result can still have `phones_pending: True`.
|
|
@@ -60,13 +60,13 @@ try:
|
|
|
60
60
|
|
|
61
61
|
SDK_VERSION = _pkg_version("oneshot-python")
|
|
62
62
|
except Exception: # pragma: no cover - editable/source runs without dist metadata
|
|
63
|
-
SDK_VERSION = "0.
|
|
63
|
+
SDK_VERSION = "0.40.0"
|
|
64
64
|
|
|
65
65
|
_BASE_URL = "https://win.oneshotagent.com"
|
|
66
66
|
_HUNT_BASE_URL = "https://api.soulhunt.ai"
|
|
67
67
|
_ACCESS_TOKEN_RE = re.compile(r"^oneshot_[0-9a-f]{64}$")
|
|
68
68
|
# Endpoints whose admission reads a "submit"-scoped agent proof (same list as the TypeScript SDK).
|
|
69
|
-
_DURABLE_ADMISSION_ENDPOINT = re.compile(r"(
|
|
69
|
+
_DURABLE_ADMISSION_ENDPOINT = re.compile(r"(lookup/person-(profile|email|email-status)|lookup/place-permits|physical-mail/send)$")
|
|
70
70
|
_CHAIN_ID = 8453
|
|
71
71
|
_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
72
72
|
|
|
@@ -917,6 +917,7 @@ class OneShotClient:
|
|
|
917
917
|
on the ``x-quote-id`` header from the very first attempt, since
|
|
918
918
|
quote-based routes reject before the 402 dance even starts without it.
|
|
919
919
|
"""
|
|
920
|
+
person_quote = endpoint in ("/v1/tools/find/person", "/v1/tools/lookup/person-profile", "/v1/tools/lookup/person-dossier")
|
|
920
921
|
memo = payload.get("memo")
|
|
921
922
|
if memo is not None:
|
|
922
923
|
if not isinstance(memo, str) or not memo.strip():
|
|
@@ -999,7 +1000,7 @@ class OneShotClient:
|
|
|
999
1000
|
"processing",
|
|
1000
1001
|
):
|
|
1001
1002
|
if not wait:
|
|
1002
|
-
return result if endpoint.endswith(("/linkedin/invite", "/linkedin/invite/withdraw")) else {"request_id": result["request_id"], "status": result["status"]}
|
|
1003
|
+
return result if person_quote or endpoint.endswith(("/linkedin/invite", "/linkedin/invite/withdraw")) else {"request_id": result["request_id"], "status": result["status"]}
|
|
1003
1004
|
return await self._poll_job(
|
|
1004
1005
|
client,
|
|
1005
1006
|
result["request_id"],
|
|
@@ -1039,6 +1040,9 @@ class OneShotClient:
|
|
|
1039
1040
|
f"Quote ${total} exceeds max_cost ${max_cost}"
|
|
1040
1041
|
)
|
|
1041
1042
|
|
|
1043
|
+
if person_quote and total is not None:
|
|
1044
|
+
self._assert_within_budget(total)
|
|
1045
|
+
|
|
1042
1046
|
self._log(f"Payment required: {payment_request['amount']} USDC")
|
|
1043
1047
|
|
|
1044
1048
|
if self._access_token:
|
|
@@ -1068,7 +1072,7 @@ class OneShotClient:
|
|
|
1068
1072
|
result = resp2.json()
|
|
1069
1073
|
if isinstance(result, dict) and result.get("request_id") and result.get("status") in ("pending", "processing"):
|
|
1070
1074
|
if not wait:
|
|
1071
|
-
return result if endpoint.endswith(("/linkedin/invite", "/linkedin/invite/withdraw")) else {"request_id": result["request_id"], "status": result["status"]}
|
|
1075
|
+
return result if person_quote or endpoint.endswith(("/linkedin/invite", "/linkedin/invite/withdraw")) else {"request_id": result["request_id"], "status": result["status"]}
|
|
1072
1076
|
return await self._poll_job(
|
|
1073
1077
|
client, result["request_id"], timeout_sec,
|
|
1074
1078
|
wait_for_phones=wait_for_phones, phone_timeout_sec=phone_timeout_sec,
|
|
@@ -1158,7 +1162,7 @@ class OneShotClient:
|
|
|
1158
1162
|
):
|
|
1159
1163
|
self._log(f"Job queued: {result['request_id']}")
|
|
1160
1164
|
if not wait:
|
|
1161
|
-
return result if endpoint.endswith(("/linkedin/invite", "/linkedin/invite/withdraw")) else {"request_id": result["request_id"], "status": result["status"]}
|
|
1165
|
+
return result if person_quote or endpoint.endswith(("/linkedin/invite", "/linkedin/invite/withdraw")) else {"request_id": result["request_id"], "status": result["status"]}
|
|
1162
1166
|
return await self._poll_job(
|
|
1163
1167
|
client,
|
|
1164
1168
|
result["request_id"],
|
|
@@ -2073,37 +2077,49 @@ class OneShotClient:
|
|
|
2073
2077
|
approval_id = kwargs.pop("approval_id", None)
|
|
2074
2078
|
return await self.acall_tool("/v1/tools/sms/send", {"message": message, "to_number": to_number, **kwargs}, approval_id=approval_id)
|
|
2075
2079
|
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
def
|
|
2085
|
-
"""
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2080
|
+
@staticmethod
|
|
2081
|
+
def _person_call_options(kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
2082
|
+
"""Keep SDK controls out of the canonical quoted input."""
|
|
2083
|
+
return {key: kwargs.pop(key) for key in (
|
|
2084
|
+
"max_cost", "timeout_sec", "wait", "wait_for_phones",
|
|
2085
|
+
"phone_timeout_sec", "idempotency_key", "approval_id",
|
|
2086
|
+
) if key in kwargs}
|
|
2087
|
+
|
|
2088
|
+
def person_find(self, *, include_phone: bool = False, **kwargs: Any) -> Any:
|
|
2089
|
+
"""Phone lookup is off by default; opt-in uses the limit (default 100) and the per-person add-on on the pricing page, with no hit guaranteed."""
|
|
2090
|
+
options = self._person_call_options(kwargs)
|
|
2091
|
+
return self.call_tool("/v1/tools/find/person", {**kwargs, "include_phone": include_phone}, **options)
|
|
2092
|
+
|
|
2093
|
+
async def aperson_find(self, *, include_phone: bool = False, **kwargs: Any) -> Any:
|
|
2094
|
+
"""Phone lookup is off by default; opt-in uses the limit (default 100) and the per-person add-on on the pricing page, with no hit guaranteed."""
|
|
2095
|
+
options = self._person_call_options(kwargs)
|
|
2096
|
+
return await self.acall_tool("/v1/tools/find/person", {**kwargs, "include_phone": include_phone}, **options)
|
|
2097
|
+
|
|
2098
|
+
def person_profile(self, *, include_phone: bool = False, **kwargs: Any) -> Any:
|
|
2099
|
+
"""Phone lookup is off by default; opt-in uses the add-on on the pricing page for one person, with no hit guaranteed."""
|
|
2100
|
+
options = self._person_call_options(kwargs)
|
|
2101
|
+
return self.call_tool("/v1/tools/lookup/person-profile", {**kwargs, "include_phone": include_phone}, **options)
|
|
2102
|
+
|
|
2103
|
+
async def aperson_profile(self, *, include_phone: bool = False, **kwargs: Any) -> Any:
|
|
2104
|
+
"""Phone lookup is off by default; opt-in uses the add-on on the pricing page for one person, with no hit guaranteed."""
|
|
2105
|
+
options = self._person_call_options(kwargs)
|
|
2106
|
+
return await self.acall_tool("/v1/tools/lookup/person-profile", {**kwargs, "include_phone": include_phone}, **options)
|
|
2107
|
+
|
|
2108
|
+
def company_find(self, **kwargs: Any) -> Any:
|
|
2093
2109
|
"""Search for companies by name, domain, industry, location, size, etc. Blocking."""
|
|
2094
|
-
return self.call_tool("/v1/tools/
|
|
2110
|
+
return self.call_tool("/v1/tools/find/company", kwargs)
|
|
2095
2111
|
|
|
2096
|
-
async def
|
|
2112
|
+
async def acompany_find(self, **kwargs: Any) -> Any:
|
|
2097
2113
|
"""Search for companies by name, domain, industry, location, size, etc. Async."""
|
|
2098
|
-
return await self.acall_tool("/v1/tools/
|
|
2114
|
+
return await self.acall_tool("/v1/tools/find/company", kwargs)
|
|
2099
2115
|
|
|
2100
|
-
def
|
|
2116
|
+
def company_profile(self, **kwargs: Any) -> Any:
|
|
2101
2117
|
"""Enrich a company from domain, name, LinkedIn URL, or ticker. Blocking."""
|
|
2102
|
-
return self.call_tool("/v1/tools/
|
|
2118
|
+
return self.call_tool("/v1/tools/lookup/company-profile", kwargs)
|
|
2103
2119
|
|
|
2104
|
-
async def
|
|
2120
|
+
async def acompany_profile(self, **kwargs: Any) -> Any:
|
|
2105
2121
|
"""Enrich a company from domain, name, LinkedIn URL, or ticker. Async."""
|
|
2106
|
-
return await self.acall_tool("/v1/tools/
|
|
2122
|
+
return await self.acall_tool("/v1/tools/lookup/company-profile", kwargs)
|
|
2107
2123
|
|
|
2108
2124
|
def business_find(self, **kwargs: Any) -> Any:
|
|
2109
2125
|
"""Discover local businesses (restaurants, contractors, practices) by category/keywords x location. Blocking."""
|
|
@@ -2129,7 +2145,7 @@ class OneShotClient:
|
|
|
2129
2145
|
"""Federal Sources Sought / Presolicitation notices (SAM.gov) by NAICS code with the contracting officer's contact. Result carries `data_as_of` (snapshot behind the rows). Async."""
|
|
2130
2146
|
return await self.acall_tool("/v1/tools/find/record-solicitations", {"naics": naics, **kwargs})
|
|
2131
2147
|
|
|
2132
|
-
def
|
|
2148
|
+
def person_email(self, company_domain: str, *, full_name: Optional[str] = None, first_name: Optional[str] = None, last_name: Optional[str] = None, **kwargs: Any) -> Any:
|
|
2133
2149
|
"""Find a person's email address. Blocking."""
|
|
2134
2150
|
payload: dict[str, Any] = {"company_domain": company_domain, **kwargs}
|
|
2135
2151
|
if full_name:
|
|
@@ -2138,9 +2154,9 @@ class OneShotClient:
|
|
|
2138
2154
|
payload["first_name"] = first_name
|
|
2139
2155
|
if last_name:
|
|
2140
2156
|
payload["last_name"] = last_name
|
|
2141
|
-
return self.call_tool("/v1/tools/
|
|
2157
|
+
return self.call_tool("/v1/tools/lookup/person-email", payload)
|
|
2142
2158
|
|
|
2143
|
-
async def
|
|
2159
|
+
async def aperson_email(self, company_domain: str, *, full_name: Optional[str] = None, first_name: Optional[str] = None, last_name: Optional[str] = None, **kwargs: Any) -> Any:
|
|
2144
2160
|
"""Find a person's email address. Async."""
|
|
2145
2161
|
payload: dict[str, Any] = {"company_domain": company_domain, **kwargs}
|
|
2146
2162
|
if full_name:
|
|
@@ -2149,71 +2165,73 @@ class OneShotClient:
|
|
|
2149
2165
|
payload["first_name"] = first_name
|
|
2150
2166
|
if last_name:
|
|
2151
2167
|
payload["last_name"] = last_name
|
|
2152
|
-
return await self.acall_tool("/v1/tools/
|
|
2168
|
+
return await self.acall_tool("/v1/tools/lookup/person-email", payload)
|
|
2153
2169
|
|
|
2154
|
-
def
|
|
2170
|
+
def person_email_status(self, email: str) -> Any:
|
|
2155
2171
|
"""Verify email deliverability. Blocking."""
|
|
2156
|
-
return self.call_tool("/v1/tools/
|
|
2172
|
+
return self.call_tool("/v1/tools/lookup/person-email-status", {"email": email})
|
|
2157
2173
|
|
|
2158
|
-
async def
|
|
2174
|
+
async def aperson_email_status(self, email: str) -> Any:
|
|
2159
2175
|
"""Verify email deliverability. Async."""
|
|
2160
|
-
return await self.acall_tool("/v1/tools/
|
|
2176
|
+
return await self.acall_tool("/v1/tools/lookup/person-email-status", {"email": email})
|
|
2161
2177
|
|
|
2162
|
-
def
|
|
2163
|
-
"""
|
|
2164
|
-
|
|
2178
|
+
def person_dossier(self, *, include_phone: bool = False, **kwargs: Any) -> Any:
|
|
2179
|
+
"""Phone lookup is off by default; opt-in uses the add-on on the pricing page for one person, with no hit guaranteed."""
|
|
2180
|
+
options = self._person_call_options(kwargs)
|
|
2181
|
+
return self.call_tool("/v1/tools/lookup/person-dossier", {**kwargs, "include_phone": include_phone}, **options)
|
|
2165
2182
|
|
|
2166
|
-
async def
|
|
2167
|
-
"""
|
|
2168
|
-
|
|
2183
|
+
async def aperson_dossier(self, *, include_phone: bool = False, **kwargs: Any) -> Any:
|
|
2184
|
+
"""Phone lookup is off by default; opt-in uses the add-on on the pricing page for one person, with no hit guaranteed."""
|
|
2185
|
+
options = self._person_call_options(kwargs)
|
|
2186
|
+
return await self.acall_tool("/v1/tools/lookup/person-dossier", {**kwargs, "include_phone": include_phone}, **options)
|
|
2169
2187
|
|
|
2170
|
-
def
|
|
2188
|
+
def person_social_accounts(self, **kwargs: Any) -> Any:
|
|
2171
2189
|
"""Discover social media profiles. Blocking."""
|
|
2172
|
-
return self.call_tool("/v1/tools/
|
|
2190
|
+
return self.call_tool("/v1/tools/lookup/person-social-accounts", kwargs)
|
|
2173
2191
|
|
|
2174
|
-
async def
|
|
2192
|
+
async def aperson_social_accounts(self, **kwargs: Any) -> Any:
|
|
2175
2193
|
"""Discover social media profiles. Async."""
|
|
2176
|
-
return await self.acall_tool("/v1/tools/
|
|
2194
|
+
return await self.acall_tool("/v1/tools/lookup/person-social-accounts", kwargs)
|
|
2177
2195
|
|
|
2178
|
-
def
|
|
2196
|
+
def person_articles(self, name: str, company: str, **kwargs: Any) -> Any:
|
|
2179
2197
|
"""Find articles by or about a person. Blocking."""
|
|
2180
|
-
return self.call_tool("/v1/tools/
|
|
2198
|
+
return self.call_tool("/v1/tools/lookup/person-articles", {"name": name, "company": company, **kwargs})
|
|
2181
2199
|
|
|
2182
|
-
async def
|
|
2200
|
+
async def aperson_articles(self, name: str, company: str, **kwargs: Any) -> Any:
|
|
2183
2201
|
"""Find articles by or about a person. Async."""
|
|
2184
|
-
return await self.acall_tool("/v1/tools/
|
|
2202
|
+
return await self.acall_tool("/v1/tools/lookup/person-articles", {"name": name, "company": company, **kwargs})
|
|
2185
2203
|
|
|
2186
|
-
def
|
|
2204
|
+
def person_posts(self, social_media_url: str, **kwargs: Any) -> Any:
|
|
2187
2205
|
"""Get recent social posts from a person. Blocking."""
|
|
2188
|
-
return self.call_tool("/v1/tools/
|
|
2206
|
+
return self.call_tool("/v1/tools/lookup/person-posts", {"social_media_url": social_media_url, **kwargs})
|
|
2189
2207
|
|
|
2190
|
-
async def
|
|
2208
|
+
async def aperson_posts(self, social_media_url: str, **kwargs: Any) -> Any:
|
|
2191
2209
|
"""Get recent social posts from a person. Async."""
|
|
2192
|
-
return await self.acall_tool("/v1/tools/
|
|
2210
|
+
return await self.acall_tool("/v1/tools/lookup/person-posts", {"social_media_url": social_media_url, **kwargs})
|
|
2193
2211
|
|
|
2194
2212
|
def person_interests(self, **kwargs: Any) -> Any:
|
|
2195
2213
|
"""Analyze a person's interests from their digital footprint. Blocking."""
|
|
2196
|
-
return self.call_tool("/v1/tools/
|
|
2214
|
+
return self.call_tool("/v1/tools/lookup/person-interests", kwargs)
|
|
2197
2215
|
|
|
2198
2216
|
async def aperson_interests(self, **kwargs: Any) -> Any:
|
|
2199
2217
|
"""Analyze a person's interests from their digital footprint. Async."""
|
|
2200
|
-
return await self.acall_tool("/v1/tools/
|
|
2218
|
+
return await self.acall_tool("/v1/tools/lookup/person-interests", kwargs)
|
|
2201
2219
|
|
|
2202
|
-
def
|
|
2220
|
+
def person_followers(self, social_media_url: str, **kwargs: Any) -> Any:
|
|
2203
2221
|
"""Get a person's social interactions. Blocking."""
|
|
2204
|
-
return self.call_tool("/v1/tools/
|
|
2222
|
+
return self.call_tool("/v1/tools/lookup/person-followers", {"social_media_url": social_media_url, **kwargs})
|
|
2205
2223
|
|
|
2206
|
-
async def
|
|
2224
|
+
async def aperson_followers(self, social_media_url: str, **kwargs: Any) -> Any:
|
|
2207
2225
|
"""Get a person's social interactions. Async."""
|
|
2208
|
-
return await self.acall_tool("/v1/tools/
|
|
2226
|
+
return await self.acall_tool("/v1/tools/lookup/person-followers", {"social_media_url": social_media_url, **kwargs})
|
|
2209
2227
|
|
|
2210
|
-
def
|
|
2228
|
+
def product_find(self, query: str, *, limit: int = 10, **kwargs: Any) -> Any:
|
|
2211
2229
|
"""Search for products. Blocking."""
|
|
2212
|
-
return self.call_tool("/v1/tools/
|
|
2230
|
+
return self.call_tool("/v1/tools/find/product", {"query": query, "limit": limit, **kwargs})
|
|
2213
2231
|
|
|
2214
|
-
async def
|
|
2232
|
+
async def aproduct_find(self, query: str, *, limit: int = 10, **kwargs: Any) -> Any:
|
|
2215
2233
|
"""Search for products. Async."""
|
|
2216
|
-
return await self.acall_tool("/v1/tools/
|
|
2234
|
+
return await self.acall_tool("/v1/tools/find/product", {"query": query, "limit": limit, **kwargs})
|
|
2217
2235
|
|
|
2218
2236
|
def commerce_buy(self, product_url: str, shipping_address: dict[str, Any], **kwargs: Any) -> Any:
|
|
2219
2237
|
"""Purchase a product. Blocking."""
|
|
@@ -2515,19 +2533,27 @@ class OneShotClient:
|
|
|
2515
2533
|
@staticmethod
|
|
2516
2534
|
def _is_phones_pending(result: Any) -> bool:
|
|
2517
2535
|
"""Detects whether a result is awaiting Apollo's async phone-reveal
|
|
2518
|
-
webhook.
|
|
2519
|
-
and
|
|
2536
|
+
webhook. Accepts worker search rows, profile objects, dossier sections
|
|
2537
|
+
and legacy flat results.
|
|
2520
2538
|
|
|
2521
2539
|
Mirrors the TS SDK's _isPhonesPending — kept in lockstep so the worker
|
|
2522
2540
|
contract works for both SDKs.
|
|
2523
2541
|
"""
|
|
2524
2542
|
if not isinstance(result, dict):
|
|
2525
2543
|
return False
|
|
2526
|
-
|
|
2527
|
-
return True
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2544
|
+
def pending(value: Any) -> bool:
|
|
2545
|
+
return isinstance(value, dict) and value.get("phones_pending") is True
|
|
2546
|
+
|
|
2547
|
+
for body in (result, result.get("result")):
|
|
2548
|
+
if not isinstance(body, dict):
|
|
2549
|
+
continue
|
|
2550
|
+
profile = body.get("profile")
|
|
2551
|
+
data = profile.get("data") if isinstance(profile, dict) else None
|
|
2552
|
+
rows = body.get("results")
|
|
2553
|
+
if pending(body) or pending(profile) or pending(data):
|
|
2554
|
+
return True
|
|
2555
|
+
if isinstance(rows, list) and any(pending(row) for row in rows):
|
|
2556
|
+
return True
|
|
2531
2557
|
return False
|
|
2532
2558
|
|
|
2533
2559
|
async def _poll_for_phones(
|