specstream-client 1.2.0__tar.gz → 1.3.1__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.
- {specstream_client-1.2.0 → specstream_client-1.3.1}/PKG-INFO +9 -8
- {specstream_client-1.2.0 → specstream_client-1.3.1}/README.md +8 -7
- {specstream_client-1.2.0 → specstream_client-1.3.1}/pyproject.toml +1 -1
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/__init__.py +1 -1
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/client.py +61 -1
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/lifecycle_commands.py +30 -1
- {specstream_client-1.2.0 → specstream_client-1.3.1}/tests/test_client.py +20 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/.gitignore +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/LICENSE +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/__main__.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/authorization.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/cli.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/families.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/specstream_client/py.typed +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/tests/__init__.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/tests/test_cli.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/tests/test_convergence.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/tests/test_http_boundary.py +0 -0
- {specstream_client-1.2.0 → specstream_client-1.3.1}/tests/test_m16_exports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: specstream-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Python client for building lead lists and company lists from the live web. Every match comes with its source, priced before it runs.
|
|
5
5
|
Project-URL: Homepage, https://specstream.ai
|
|
6
6
|
Project-URL: Documentation, https://specstream.ai/docs/sdk
|
|
@@ -29,9 +29,12 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
|
|
30
30
|
`pip install specstream-client` (the `specstream` package on PyPI is a different project)
|
|
31
31
|
|
|
32
|
-
Python client and command line for [SpecStream](https://specstream.ai), the live web data engine
|
|
32
|
+
Python client and command line for [SpecStream](https://specstream.ai), the live web data engine. One platform to build, search, extract, enrich, map, and monitor data across the entire web.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Ask for any dataset in plain words, and SpecStream builds it from the entire live web: every match found, extracted and enriched into structured records, then monitored for change.
|
|
35
|
+
|
|
36
|
+
- Source URL and read time on every value.
|
|
37
|
+
- You pay for each row delivered.
|
|
35
38
|
|
|
36
39
|
The client has no dependencies and needs Python 3.12 or newer. It uses the same account, permissions, projects and balance as the workspace.
|
|
37
40
|
|
|
@@ -92,16 +95,14 @@ If the response is lost, `get_capacity_request(saved_request_key)` returns the s
|
|
|
92
95
|
|
|
93
96
|
- `quote_capacity_work` also prices `extend` (more results), `add_facts` and `refresh` (a recheck, at half price) on a finished run. Pass the original `parent_find_id`.
|
|
94
97
|
- `spec_options(kind)` lists the facts you can ask for, per kind of thing. `edit_spec` changes a request without rereading it.
|
|
95
|
-
- `create_capacity_schedule` keeps a request
|
|
98
|
+
- `create_capacity_schedule` keeps a request up to date, hourly to yearly, inside per-run and per-period limits.
|
|
96
99
|
- `export` and `download_export` give CSV, XLSX, JSON or HTML. `create_customer_webhook` sends signed events.
|
|
97
100
|
- Projects, members, shares and starred items have their own methods.
|
|
98
101
|
|
|
99
|
-
Kinds of thing include local businesses, companies, people, creators, products, apps, job postings, papers, clinical trials, grants, tenders, regulations, vulnerabilities, software packages, repositories, datasets, books and podcasts.
|
|
100
|
-
|
|
101
102
|
## Command line
|
|
102
103
|
|
|
103
104
|
```sh
|
|
104
|
-
specstream compile "
|
|
105
|
+
specstream compile "Open-source vector databases on GitHub with more than 5,000 stars, with their license."
|
|
105
106
|
specstream get-capacity-balance
|
|
106
107
|
specstream quote-capacity-work --body quote-request.json
|
|
107
108
|
specstream authorize-capacity-work --body approved-execution.json
|
|
@@ -112,7 +113,7 @@ specstream rows --find-id FIND_ID
|
|
|
112
113
|
|
|
113
114
|
## Pricing
|
|
114
115
|
|
|
115
|
-
New accounts start with $10 of
|
|
116
|
+
New accounts start with $10 of credit for 30 days, no card. A delivered result costs 5 cents, 15 cents or $1 depending on how many facts it carries. No result, no charge. Rechecks cost half. People and agents share one balance. See [pricing](https://specstream.ai/pricing).
|
|
116
117
|
|
|
117
118
|
## Links
|
|
118
119
|
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
`pip install specstream-client` (the `specstream` package on PyPI is a different project)
|
|
4
4
|
|
|
5
|
-
Python client and command line for [SpecStream](https://specstream.ai), the live web data engine
|
|
5
|
+
Python client and command line for [SpecStream](https://specstream.ai), the live web data engine. One platform to build, search, extract, enrich, map, and monitor data across the entire web.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Ask for any dataset in plain words, and SpecStream builds it from the entire live web: every match found, extracted and enriched into structured records, then monitored for change.
|
|
8
|
+
|
|
9
|
+
- Source URL and read time on every value.
|
|
10
|
+
- You pay for each row delivered.
|
|
8
11
|
|
|
9
12
|
The client has no dependencies and needs Python 3.12 or newer. It uses the same account, permissions, projects and balance as the workspace.
|
|
10
13
|
|
|
@@ -65,16 +68,14 @@ If the response is lost, `get_capacity_request(saved_request_key)` returns the s
|
|
|
65
68
|
|
|
66
69
|
- `quote_capacity_work` also prices `extend` (more results), `add_facts` and `refresh` (a recheck, at half price) on a finished run. Pass the original `parent_find_id`.
|
|
67
70
|
- `spec_options(kind)` lists the facts you can ask for, per kind of thing. `edit_spec` changes a request without rereading it.
|
|
68
|
-
- `create_capacity_schedule` keeps a request
|
|
71
|
+
- `create_capacity_schedule` keeps a request up to date, hourly to yearly, inside per-run and per-period limits.
|
|
69
72
|
- `export` and `download_export` give CSV, XLSX, JSON or HTML. `create_customer_webhook` sends signed events.
|
|
70
73
|
- Projects, members, shares and starred items have their own methods.
|
|
71
74
|
|
|
72
|
-
Kinds of thing include local businesses, companies, people, creators, products, apps, job postings, papers, clinical trials, grants, tenders, regulations, vulnerabilities, software packages, repositories, datasets, books and podcasts.
|
|
73
|
-
|
|
74
75
|
## Command line
|
|
75
76
|
|
|
76
77
|
```sh
|
|
77
|
-
specstream compile "
|
|
78
|
+
specstream compile "Open-source vector databases on GitHub with more than 5,000 stars, with their license."
|
|
78
79
|
specstream get-capacity-balance
|
|
79
80
|
specstream quote-capacity-work --body quote-request.json
|
|
80
81
|
specstream authorize-capacity-work --body approved-execution.json
|
|
@@ -85,7 +86,7 @@ specstream rows --find-id FIND_ID
|
|
|
85
86
|
|
|
86
87
|
## Pricing
|
|
87
88
|
|
|
88
|
-
New accounts start with $10 of
|
|
89
|
+
New accounts start with $10 of credit for 30 days, no card. A delivered result costs 5 cents, 15 cents or $1 depending on how many facts it carries. No result, no charge. Rechecks cost half. People and agents share one balance. See [pricing](https://specstream.ai/pricing).
|
|
89
90
|
|
|
90
91
|
## Links
|
|
91
92
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "specstream-client"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.1"
|
|
4
4
|
description = "Python client for building lead lists and company lists from the live web. Every match comes with its source, priced before it runs."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -15,7 +15,7 @@ from specstream_client.authorization import require_work_authorization
|
|
|
15
15
|
from specstream_client.families import FAMILIES
|
|
16
16
|
|
|
17
17
|
DEFAULT_BASE_URL = "https://api.specstream.ai"
|
|
18
|
-
USER_AGENT = "specstream-python/1.
|
|
18
|
+
USER_AGENT = "specstream-python/1.3.0"
|
|
19
19
|
|
|
20
20
|
OpenFn = Callable[[urllib.request.Request, float], Any]
|
|
21
21
|
|
|
@@ -268,6 +268,64 @@ class Client:
|
|
|
268
268
|
path += "?" + encoded
|
|
269
269
|
return self._request("GET", path, None)
|
|
270
270
|
|
|
271
|
+
def web_search(self, *, q: str, passages: bool | None = None) -> dict[str, Any]:
|
|
272
|
+
"""
|
|
273
|
+
Fast search: answer a question from the live web in seconds, each value with its source.
|
|
274
|
+
"""
|
|
275
|
+
path = "/v1/web-search"
|
|
276
|
+
query: dict[str, Any] = {
|
|
277
|
+
"q": q,
|
|
278
|
+
"passages": passages,
|
|
279
|
+
}
|
|
280
|
+
encoded = urlencode(
|
|
281
|
+
{
|
|
282
|
+
key: str(value).lower() if isinstance(value, bool) else value
|
|
283
|
+
for key, value in query.items()
|
|
284
|
+
if value is not None
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
if encoded:
|
|
288
|
+
path += "?" + encoded
|
|
289
|
+
return self._request("GET", path, None)
|
|
290
|
+
|
|
291
|
+
def extract_pages(self, request: dict[str, Any]) -> dict[str, Any]:
|
|
292
|
+
"""
|
|
293
|
+
Read up to 20 pages live: each page's main text as Markdown or plain text, its title and
|
|
294
|
+
its published date. Only a page read is charged.
|
|
295
|
+
"""
|
|
296
|
+
path = "/v1/extract"
|
|
297
|
+
return self._request("POST", path, request)
|
|
298
|
+
|
|
299
|
+
def web_search_answers(
|
|
300
|
+
self,
|
|
301
|
+
*,
|
|
302
|
+
q: str | None = None,
|
|
303
|
+
limit: int | None = None,
|
|
304
|
+
cursor: str | None = None,
|
|
305
|
+
) -> dict[str, Any]:
|
|
306
|
+
"""List your web search history: each question, its answer and its sources, newest first."""
|
|
307
|
+
path = "/v1/web-search/answers"
|
|
308
|
+
query: dict[str, Any] = {
|
|
309
|
+
"q": q,
|
|
310
|
+
"limit": limit,
|
|
311
|
+
"cursor": cursor,
|
|
312
|
+
}
|
|
313
|
+
encoded = urlencode(
|
|
314
|
+
{
|
|
315
|
+
key: str(value).lower() if isinstance(value, bool) else value
|
|
316
|
+
for key, value in query.items()
|
|
317
|
+
if value is not None
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
if encoded:
|
|
321
|
+
path += "?" + encoded
|
|
322
|
+
return self._request("GET", path, None)
|
|
323
|
+
|
|
324
|
+
def web_search_answer(self, answer_id: str) -> dict[str, Any]:
|
|
325
|
+
"""Open one kept web search answer as it was given."""
|
|
326
|
+
path = f"/v1/web-search/answers/{quote_path(answer_id, safe='')}"
|
|
327
|
+
return self._request("GET", path, None)
|
|
328
|
+
|
|
271
329
|
def workspace_list_notifications(
|
|
272
330
|
self,
|
|
273
331
|
*,
|
|
@@ -710,6 +768,7 @@ class Client:
|
|
|
710
768
|
project_id: str | None = None,
|
|
711
769
|
cursor: str | None = None,
|
|
712
770
|
limit: int | None = None,
|
|
771
|
+
format: str | None = None,
|
|
713
772
|
) -> dict[str, Any]:
|
|
714
773
|
"""Read where the request asked with this key stands and the rows it found so far."""
|
|
715
774
|
path = f"/v1/capacity/requests/{quote_path(request_key, safe='')}/rows"
|
|
@@ -717,6 +776,7 @@ class Client:
|
|
|
717
776
|
"project_id": project_id,
|
|
718
777
|
"cursor": cursor,
|
|
719
778
|
"limit": limit,
|
|
779
|
+
"format": format,
|
|
720
780
|
}
|
|
721
781
|
encoded = urlencode(
|
|
722
782
|
{
|
|
@@ -28,6 +28,34 @@ COMMANDS = {'get-vocabulary': {'method': 'get_vocabulary',
|
|
|
28
28
|
'body_required': False,
|
|
29
29
|
'query': {'q': 'string', 'limit': 'integer', 'cursor': 'string'},
|
|
30
30
|
'summary': 'Search the requests, lists and sources you can open'},
|
|
31
|
+
'web-search': {'method': 'web_search',
|
|
32
|
+
'arguments': [],
|
|
33
|
+
'body': False,
|
|
34
|
+
'body_required': False,
|
|
35
|
+
'query': {'q': 'string', 'passages': 'boolean'},
|
|
36
|
+
'summary': 'Fast search: answer a question from the live web in seconds, each '
|
|
37
|
+
'value with its source'},
|
|
38
|
+
'extract-pages': {'method': 'extract_pages',
|
|
39
|
+
'arguments': [],
|
|
40
|
+
'body': True,
|
|
41
|
+
'body_required': True,
|
|
42
|
+
'query': {},
|
|
43
|
+
'summary': "Read up to 20 pages live: each page's main text as Markdown or "
|
|
44
|
+
'plain text, its title and its published date. Only a page read is '
|
|
45
|
+
'charged'},
|
|
46
|
+
'web-search-answers': {'method': 'web_search_answers',
|
|
47
|
+
'arguments': [],
|
|
48
|
+
'body': False,
|
|
49
|
+
'body_required': False,
|
|
50
|
+
'query': {'q': 'string', 'limit': 'integer', 'cursor': 'string'},
|
|
51
|
+
'summary': 'List your web search history: each question, its answer and '
|
|
52
|
+
'its sources, newest first'},
|
|
53
|
+
'web-search-answer': {'method': 'web_search_answer',
|
|
54
|
+
'arguments': ['answer_id'],
|
|
55
|
+
'body': False,
|
|
56
|
+
'body_required': False,
|
|
57
|
+
'query': {},
|
|
58
|
+
'summary': 'Open one kept web search answer as it was given'},
|
|
31
59
|
'workspace-list-notifications': {'method': 'workspace_list_notifications',
|
|
32
60
|
'arguments': [],
|
|
33
61
|
'body': False,
|
|
@@ -342,7 +370,8 @@ COMMANDS = {'get-vocabulary': {'method': 'get_vocabulary',
|
|
|
342
370
|
'body_required': False,
|
|
343
371
|
'query': {'project_id': 'string',
|
|
344
372
|
'cursor': 'string',
|
|
345
|
-
'limit': 'integer'
|
|
373
|
+
'limit': 'integer',
|
|
374
|
+
'format': 'string'},
|
|
346
375
|
'summary': 'Read where the request asked with this key stands and '
|
|
347
376
|
'the rows it found so far'},
|
|
348
377
|
'get-capacity-execution': {'method': 'get_capacity_execution',
|
|
@@ -95,3 +95,23 @@ class ClientTests(unittest.TestCase):
|
|
|
95
95
|
self.assertEqual(client.authorize_capacity_work(approval)["find_id"], "fnd_fixture")
|
|
96
96
|
self.assertEqual(client.get_capacity_request("stable-review-001")["find_id"], "fnd_fixture")
|
|
97
97
|
self.assertFalse(any("/funding" in path or "/run" in path for _, path, _ in calls))
|
|
98
|
+
|
|
99
|
+
def test_web_search_returns_the_answers_kind_and_sources(self):
|
|
100
|
+
page = {
|
|
101
|
+
"request_id": "call-1",
|
|
102
|
+
"kind": "list",
|
|
103
|
+
"answer": None,
|
|
104
|
+
"rows": [{"name": "A", "page": "https://example.org/a", "sources": [0]}],
|
|
105
|
+
"sources": [{"url": "https://example.org/a", "cited": True}],
|
|
106
|
+
}
|
|
107
|
+
urls = []
|
|
108
|
+
|
|
109
|
+
def opener(request, timeout):
|
|
110
|
+
urls.append((request.get_method(), request.full_url))
|
|
111
|
+
return _FakeResponse(page)
|
|
112
|
+
|
|
113
|
+
found = Client("fixture-key", opener=opener).web_search(q="roasters in Lyon")
|
|
114
|
+
self.assertEqual(urls, [("GET", urls[0][1])])
|
|
115
|
+
self.assertTrue(urls[0][1].endswith("/v1/web-search?q=roasters+in+Lyon"))
|
|
116
|
+
self.assertEqual((found["kind"], found["sources"]), (page["kind"], page["sources"]))
|
|
117
|
+
self.assertEqual(found["rows"][0]["sources"], [0])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|