exa-py 1.14.9__tar.gz → 1.14.10__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.
Potentially problematic release.
This version of exa-py might be problematic. Click here for more details.
- {exa_py-1.14.9 → exa_py-1.14.10}/PKG-INFO +19 -14
- {exa_py-1.14.9 → exa_py-1.14.10}/README.md +2 -2
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/api.py +4 -4
- exa_py-1.14.10/exa_py/websets/_generator/pydantic/BaseModel.jinja2 +42 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/pyproject.toml +1 -1
- exa_py-1.14.9/exa_py.egg-info/PKG-INFO +0 -128
- exa_py-1.14.9/exa_py.egg-info/SOURCES.txt +0 -38
- exa_py-1.14.9/exa_py.egg-info/dependency_links.txt +0 -1
- exa_py-1.14.9/exa_py.egg-info/requires.txt +0 -5
- exa_py-1.14.9/exa_py.egg-info/top_level.txt +0 -1
- exa_py-1.14.9/setup.cfg +0 -4
- exa_py-1.14.9/setup.py +0 -26
- exa_py-1.14.9/tests/test_search_api.py +0 -136
- exa_py-1.14.9/tests/test_websets.py +0 -418
- exa_py-1.14.9/tests/test_websets_imports.py +0 -439
- exa_py-1.14.9/tests/test_websets_monitors.py +0 -502
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/py.typed +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/research/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/research/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/research/models.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/utils.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/core/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/core/base.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/enrichments/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/enrichments/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/imports/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/imports/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/items/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/items/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/monitors/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/monitors/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/monitors/runs/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/monitors/runs/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/searches/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/searches/client.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/types.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/webhooks/__init__.py +0 -0
- {exa_py-1.14.9 → exa_py-1.14.10}/exa_py/websets/webhooks/client.py +0 -0
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: exa-py
|
|
3
|
-
Version: 1.14.
|
|
3
|
+
Version: 1.14.10
|
|
4
4
|
Summary: Python SDK for Exa API.
|
|
5
|
-
Home-page: https://github.com/exa-labs/exa-py
|
|
6
|
-
Author: Exa
|
|
7
|
-
Author-email: Exa AI <hello@exa.ai>
|
|
8
5
|
License: MIT
|
|
6
|
+
Author: Exa AI
|
|
7
|
+
Author-email: hello@exa.ai
|
|
9
8
|
Requires-Python: >=3.9
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Dist: httpx (>=0.28.1)
|
|
17
|
+
Requires-Dist: openai (>=1.48)
|
|
18
|
+
Requires-Dist: pydantic (>=2.10.6)
|
|
19
|
+
Requires-Dist: requests (>=2.32.3)
|
|
20
|
+
Requires-Dist: typing-extensions (>=4.12.2)
|
|
10
21
|
Description-Content-Type: text/markdown
|
|
11
|
-
Requires-Dist: requests>=2.32.3
|
|
12
|
-
Requires-Dist: typing-extensions>=4.12.2
|
|
13
|
-
Requires-Dist: openai>=1.48
|
|
14
|
-
Requires-Dist: pydantic>=2.10.6
|
|
15
|
-
Requires-Dist: httpx>=0.28.1
|
|
16
|
-
Dynamic: author
|
|
17
|
-
Dynamic: home-page
|
|
18
22
|
|
|
19
23
|
# Exa
|
|
20
24
|
|
|
@@ -81,8 +85,8 @@ exa = Exa(api_key="your-api-key")
|
|
|
81
85
|
# basic answer
|
|
82
86
|
response = exa.answer("This is a query to answer a question")
|
|
83
87
|
|
|
84
|
-
# answer with full text
|
|
85
|
-
response = exa.answer("This is a query to answer a question", text=True
|
|
88
|
+
# answer with full text
|
|
89
|
+
response = exa.answer("This is a query to answer a question", text=True)
|
|
86
90
|
|
|
87
91
|
# answer with streaming
|
|
88
92
|
response = exa.stream_answer("This is a query to answer:")
|
|
@@ -126,3 +130,4 @@ exa = Exa(api_key="your-api-key")
|
|
|
126
130
|
output_schema=OUTPUT_SCHEMA,
|
|
127
131
|
)
|
|
128
132
|
```
|
|
133
|
+
|
|
@@ -63,8 +63,8 @@ exa = Exa(api_key="your-api-key")
|
|
|
63
63
|
# basic answer
|
|
64
64
|
response = exa.answer("This is a query to answer a question")
|
|
65
65
|
|
|
66
|
-
# answer with full text
|
|
67
|
-
response = exa.answer("This is a query to answer a question", text=True
|
|
66
|
+
# answer with full text
|
|
67
|
+
response = exa.answer("This is a query to answer a question", text=True)
|
|
68
68
|
|
|
69
69
|
# answer with streaming
|
|
70
70
|
response = exa.stream_answer("This is a query to answer:")
|
|
@@ -1909,7 +1909,7 @@ class Exa:
|
|
|
1909
1909
|
query (str): The query to answer.
|
|
1910
1910
|
text (bool, optional): Whether to include full text in the results. Defaults to False.
|
|
1911
1911
|
system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
|
|
1912
|
-
model (str, optional): The model to use for answering.
|
|
1912
|
+
model (str, optional): The model to use for answering. Defaults to None.
|
|
1913
1913
|
output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
|
|
1914
1914
|
|
|
1915
1915
|
Returns:
|
|
@@ -1948,7 +1948,7 @@ class Exa:
|
|
|
1948
1948
|
query (str): The query to answer.
|
|
1949
1949
|
text (bool): Whether to include full text in the results. Defaults to False.
|
|
1950
1950
|
system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
|
|
1951
|
-
model (str, optional): The model to use for answering.
|
|
1951
|
+
model (str, optional): The model to use for answering. Defaults to None.
|
|
1952
1952
|
output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
|
|
1953
1953
|
Returns:
|
|
1954
1954
|
StreamAnswerResponse: An object that can be iterated over to retrieve (partial text, partial citations).
|
|
@@ -2253,7 +2253,7 @@ class AsyncExa(Exa):
|
|
|
2253
2253
|
query (str): The query to answer.
|
|
2254
2254
|
text (bool, optional): Whether to include full text in the results. Defaults to False.
|
|
2255
2255
|
system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
|
|
2256
|
-
model (str, optional): The model to use for answering.
|
|
2256
|
+
model (str, optional): The model to use for answering. Defaults to None.
|
|
2257
2257
|
output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
|
|
2258
2258
|
|
|
2259
2259
|
Returns:
|
|
@@ -2292,7 +2292,7 @@ class AsyncExa(Exa):
|
|
|
2292
2292
|
query (str): The query to answer.
|
|
2293
2293
|
text (bool): Whether to include full text in the results. Defaults to False.
|
|
2294
2294
|
system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
|
|
2295
|
-
model (str, optional): The model to use for answering.
|
|
2295
|
+
model (str, optional): The model to use for answering. Defaults to None.
|
|
2296
2296
|
output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
|
|
2297
2297
|
Returns:
|
|
2298
2298
|
AsyncStreamAnswerResponse: An object that can be iterated over to retrieve (partial text, partial citations).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{% for decorator in decorators -%}
|
|
2
|
+
{{ decorator }}
|
|
3
|
+
{% endfor -%}
|
|
4
|
+
class {{ class_name }}({{ base_class }}):{% if comment is defined %} # {{ comment }}{% endif %}
|
|
5
|
+
{%- if description %}
|
|
6
|
+
"""
|
|
7
|
+
{{ description | indent(4) }}
|
|
8
|
+
"""
|
|
9
|
+
{%- endif %}
|
|
10
|
+
{%- if not fields and not description %}
|
|
11
|
+
pass
|
|
12
|
+
{%- endif %}
|
|
13
|
+
{%- if config %}
|
|
14
|
+
{%- filter indent(4) %}
|
|
15
|
+
{%- endfilter %}
|
|
16
|
+
{%- endif %}
|
|
17
|
+
{%- for field in fields -%}
|
|
18
|
+
{%- if field.name == "type" and field.field %}
|
|
19
|
+
type: Literal['{{ field.default }}']
|
|
20
|
+
{%- elif field.name == "object" and field.field %}
|
|
21
|
+
object: Literal['{{ field.default }}']
|
|
22
|
+
{%- elif not field.annotated and field.field %}
|
|
23
|
+
{{ field.name }}: {{ field.type_hint }} = {{ field.field }}
|
|
24
|
+
{%- else %}
|
|
25
|
+
{%- if field.annotated %}
|
|
26
|
+
{{ field.name }}: {{ field.annotated }}
|
|
27
|
+
{%- else %}
|
|
28
|
+
{{ field.name }}: {{ field.type_hint }}
|
|
29
|
+
{%- endif %}
|
|
30
|
+
{%- if not (field.required or (field.represented_default == 'None' and field.strip_default_none)) or field.data_type.is_optional
|
|
31
|
+
%} = {{ field.represented_default }}
|
|
32
|
+
{%- endif -%}
|
|
33
|
+
{%- endif %}
|
|
34
|
+
{%- if field.docstring %}
|
|
35
|
+
"""
|
|
36
|
+
{{ field.docstring | indent(4) }}
|
|
37
|
+
"""
|
|
38
|
+
{%- endif %}
|
|
39
|
+
{%- for method in methods -%}
|
|
40
|
+
{{ method }}
|
|
41
|
+
{%- endfor -%}
|
|
42
|
+
{%- endfor -%}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: exa-py
|
|
3
|
-
Version: 1.14.9
|
|
4
|
-
Summary: Python SDK for Exa API.
|
|
5
|
-
Home-page: https://github.com/exa-labs/exa-py
|
|
6
|
-
Author: Exa
|
|
7
|
-
Author-email: Exa AI <hello@exa.ai>
|
|
8
|
-
License: MIT
|
|
9
|
-
Requires-Python: >=3.9
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
Requires-Dist: requests>=2.32.3
|
|
12
|
-
Requires-Dist: typing-extensions>=4.12.2
|
|
13
|
-
Requires-Dist: openai>=1.48
|
|
14
|
-
Requires-Dist: pydantic>=2.10.6
|
|
15
|
-
Requires-Dist: httpx>=0.28.1
|
|
16
|
-
Dynamic: author
|
|
17
|
-
Dynamic: home-page
|
|
18
|
-
|
|
19
|
-
# Exa
|
|
20
|
-
|
|
21
|
-
Exa (formerly Metaphor) API in Python
|
|
22
|
-
|
|
23
|
-
Note: This API is basically the same as `metaphor-python` but reflects new
|
|
24
|
-
features associated with Metaphor's rename to Exa. New site is https://exa.ai
|
|
25
|
-
|
|
26
|
-
## Installation
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pip install exa_py
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Usage
|
|
33
|
-
|
|
34
|
-
Import the package and initialize the Exa client with your API key:
|
|
35
|
-
|
|
36
|
-
```python
|
|
37
|
-
from exa_py import Exa
|
|
38
|
-
|
|
39
|
-
exa = Exa(api_key="your-api-key")
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Common requests
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
|
|
46
|
-
# basic search
|
|
47
|
-
results = exa.search("This is a Exa query:")
|
|
48
|
-
|
|
49
|
-
# keyword search (non-neural)
|
|
50
|
-
results = exa.search("Google-style query", type="keyword")
|
|
51
|
-
|
|
52
|
-
# search with date filters
|
|
53
|
-
results = exa.search("This is a Exa query:", start_published_date="2019-01-01", end_published_date="2019-01-31")
|
|
54
|
-
|
|
55
|
-
# search with domain filters
|
|
56
|
-
results = exa.search("This is a Exa query:", include_domains=["www.cnn.com", "www.nytimes.com"])
|
|
57
|
-
|
|
58
|
-
# search and get text contents
|
|
59
|
-
results = exa.search_and_contents("This is a Exa query:")
|
|
60
|
-
|
|
61
|
-
# search and get contents with contents options
|
|
62
|
-
results = exa.search_and_contents("This is a Exa query:",
|
|
63
|
-
text={"include_html_tags": True, "max_characters": 1000})
|
|
64
|
-
|
|
65
|
-
# find similar documents
|
|
66
|
-
results = exa.find_similar("https://example.com")
|
|
67
|
-
|
|
68
|
-
# find similar excluding source domain
|
|
69
|
-
results = exa.find_similar("https://example.com", exclude_source_domain=True)
|
|
70
|
-
|
|
71
|
-
# find similar with contents
|
|
72
|
-
results = exa.find_similar_and_contents("https://example.com", text=True)
|
|
73
|
-
|
|
74
|
-
# get text contents
|
|
75
|
-
results = exa.get_contents(["tesla.com"])
|
|
76
|
-
|
|
77
|
-
# get contents with contents options
|
|
78
|
-
results = exa.get_contents(["urls"],
|
|
79
|
-
text={"include_html_tags": True, "max_characters": 1000})
|
|
80
|
-
|
|
81
|
-
# basic answer
|
|
82
|
-
response = exa.answer("This is a query to answer a question")
|
|
83
|
-
|
|
84
|
-
# answer with full text, using the exa-pro model (sends 2 expanded quries to exa search)
|
|
85
|
-
response = exa.answer("This is a query to answer a question", text=True, model="exa-pro")
|
|
86
|
-
|
|
87
|
-
# answer with streaming
|
|
88
|
-
response = exa.stream_answer("This is a query to answer:")
|
|
89
|
-
|
|
90
|
-
# Print each chunk as it arrives when using the stream_answer method
|
|
91
|
-
for chunk in response:
|
|
92
|
-
print(chunk, end='', flush=True)
|
|
93
|
-
|
|
94
|
-
# research task example – answer a question with citations
|
|
95
|
-
# Example prompt & schema inspired by the TypeScript example.
|
|
96
|
-
QUESTION = (
|
|
97
|
-
"Summarize the history of San Francisco highlighting one or two major events "
|
|
98
|
-
"for each decade from 1850 to 1950"
|
|
99
|
-
)
|
|
100
|
-
OUTPUT_SCHEMA: Dict[str, Any] = {
|
|
101
|
-
"type": "object",
|
|
102
|
-
"required": ["timeline"],
|
|
103
|
-
"properties": {
|
|
104
|
-
"timeline": {
|
|
105
|
-
"type": "array",
|
|
106
|
-
"items": {
|
|
107
|
-
"type": "object",
|
|
108
|
-
"required": ["decade", "notableEvents"],
|
|
109
|
-
"properties": {
|
|
110
|
-
"decade": {
|
|
111
|
-
"type": "string",
|
|
112
|
-
"description": 'Decade label e.g. "1850s"',
|
|
113
|
-
},
|
|
114
|
-
"notableEvents": {
|
|
115
|
-
"type": "string",
|
|
116
|
-
"description": "A summary of notable events.",
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
}
|
|
123
|
-
resp = exa.research.create_task(
|
|
124
|
-
instructions=QUESTION,
|
|
125
|
-
model="exa-research",
|
|
126
|
-
output_schema=OUTPUT_SCHEMA,
|
|
127
|
-
)
|
|
128
|
-
```
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
pyproject.toml
|
|
3
|
-
setup.py
|
|
4
|
-
exa_py/__init__.py
|
|
5
|
-
exa_py/api.py
|
|
6
|
-
exa_py/py.typed
|
|
7
|
-
exa_py/utils.py
|
|
8
|
-
exa_py.egg-info/PKG-INFO
|
|
9
|
-
exa_py.egg-info/SOURCES.txt
|
|
10
|
-
exa_py.egg-info/dependency_links.txt
|
|
11
|
-
exa_py.egg-info/requires.txt
|
|
12
|
-
exa_py.egg-info/top_level.txt
|
|
13
|
-
exa_py/research/__init__.py
|
|
14
|
-
exa_py/research/client.py
|
|
15
|
-
exa_py/research/models.py
|
|
16
|
-
exa_py/websets/__init__.py
|
|
17
|
-
exa_py/websets/client.py
|
|
18
|
-
exa_py/websets/types.py
|
|
19
|
-
exa_py/websets/core/__init__.py
|
|
20
|
-
exa_py/websets/core/base.py
|
|
21
|
-
exa_py/websets/enrichments/__init__.py
|
|
22
|
-
exa_py/websets/enrichments/client.py
|
|
23
|
-
exa_py/websets/imports/__init__.py
|
|
24
|
-
exa_py/websets/imports/client.py
|
|
25
|
-
exa_py/websets/items/__init__.py
|
|
26
|
-
exa_py/websets/items/client.py
|
|
27
|
-
exa_py/websets/monitors/__init__.py
|
|
28
|
-
exa_py/websets/monitors/client.py
|
|
29
|
-
exa_py/websets/monitors/runs/__init__.py
|
|
30
|
-
exa_py/websets/monitors/runs/client.py
|
|
31
|
-
exa_py/websets/searches/__init__.py
|
|
32
|
-
exa_py/websets/searches/client.py
|
|
33
|
-
exa_py/websets/webhooks/__init__.py
|
|
34
|
-
exa_py/websets/webhooks/client.py
|
|
35
|
-
tests/test_search_api.py
|
|
36
|
-
tests/test_websets.py
|
|
37
|
-
tests/test_websets_imports.py
|
|
38
|
-
tests/test_websets_monitors.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
exa_py
|
exa_py-1.14.9/setup.cfg
DELETED
exa_py-1.14.9/setup.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from setuptools import find_packages, setup
|
|
2
|
-
|
|
3
|
-
setup(
|
|
4
|
-
name="exa_py",
|
|
5
|
-
version="1.14.9",
|
|
6
|
-
description="Python SDK for Exa API.",
|
|
7
|
-
long_description_content_type="text/markdown",
|
|
8
|
-
long_description=open("README.md").read(),
|
|
9
|
-
author="Exa",
|
|
10
|
-
author_email="hello@exa.ai",
|
|
11
|
-
package_data={"exa_py": ["py.typed"]},
|
|
12
|
-
url="https://github.com/exa-labs/exa-py",
|
|
13
|
-
packages=find_packages(),
|
|
14
|
-
install_requires=["requests", "typing-extensions", "openai>=1.10.0"],
|
|
15
|
-
classifiers=[
|
|
16
|
-
"Development Status :: 5 - Production/Stable",
|
|
17
|
-
"Intended Audience :: Developers",
|
|
18
|
-
"License :: OSI Approved :: MIT License",
|
|
19
|
-
"Typing :: Typed",
|
|
20
|
-
"Programming Language :: Python :: 3.8",
|
|
21
|
-
"Programming Language :: Python :: 3.9",
|
|
22
|
-
"Programming Language :: Python :: 3.10",
|
|
23
|
-
"Programming Language :: Python :: 3.11",
|
|
24
|
-
"Programming Language :: Python :: 3.12",
|
|
25
|
-
],
|
|
26
|
-
)
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from unittest.mock import AsyncMock, patch
|
|
3
|
-
|
|
4
|
-
import httpx
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from exa_py import Exa, AsyncExa, api as exa_api
|
|
8
|
-
|
|
9
|
-
API_KEY = os.getenv("EXA_API_KEY", "test-key")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _have_real_key() -> bool:
|
|
13
|
-
return API_KEY != "test-key" and len(API_KEY) > 10
|
|
14
|
-
|
|
15
|
-
########################################
|
|
16
|
-
# Offline unit tests (no network)
|
|
17
|
-
########################################
|
|
18
|
-
|
|
19
|
-
def test_contentstatus_parsing_offline():
|
|
20
|
-
payload_status = {"id": "u", "status": "success", "source": "cached"}
|
|
21
|
-
cs = exa_api.ContentStatus(**payload_status)
|
|
22
|
-
assert cs.id == "u" and cs.status == "success" and cs.source == "cached"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_answerresponse_accepts_dict():
|
|
26
|
-
dummy = exa_api.AnswerResult(id="1", url="u", title="t")
|
|
27
|
-
resp = exa_api.AnswerResponse(answer={"foo": "bar"}, citations=[dummy])
|
|
28
|
-
assert isinstance(resp.answer, dict) and resp.answer["foo"] == "bar"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@pytest.mark.asyncio
|
|
32
|
-
async def test_async_request_accepts_201():
|
|
33
|
-
ax = AsyncExa(API_KEY)
|
|
34
|
-
|
|
35
|
-
async def _fake_post(url, json, headers):
|
|
36
|
-
return httpx.Response(201, json={"ok": True})
|
|
37
|
-
|
|
38
|
-
with patch.object(ax.client, "post", new=AsyncMock(side_effect=_fake_post)):
|
|
39
|
-
data = await ax.async_request("/dummy", {})
|
|
40
|
-
assert data == {"ok": True}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
########################################
|
|
44
|
-
# Live integration tests (skipped without key)
|
|
45
|
-
########################################
|
|
46
|
-
|
|
47
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
48
|
-
def test_user_agent_header():
|
|
49
|
-
exa = Exa(API_KEY)
|
|
50
|
-
assert exa.headers["User-Agent"] == "exa-py 1.12.4"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
54
|
-
def test_research_client_attrs():
|
|
55
|
-
exa = Exa(API_KEY)
|
|
56
|
-
aexa = AsyncExa(API_KEY)
|
|
57
|
-
assert hasattr(exa, "research") and hasattr(aexa, "research")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# ---- Core live endpoint smoke checks ----
|
|
61
|
-
|
|
62
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
63
|
-
def test_get_contents_live_preferred():
|
|
64
|
-
exa = Exa(API_KEY)
|
|
65
|
-
resp = exa.get_contents(urls=["https://techcrunch.com"], text=True, livecrawl="preferred")
|
|
66
|
-
assert isinstance(resp, exa_api.SearchResponse)
|
|
67
|
-
# statuses may be empty when cached – still fine
|
|
68
|
-
assert len(resp.results) >= 1
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
72
|
-
def test_search_and_contents_live():
|
|
73
|
-
exa = Exa(API_KEY)
|
|
74
|
-
resp = exa.search_and_contents("openai", num_results=1, text=True)
|
|
75
|
-
assert resp.results and resp.results[0].text
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
79
|
-
def test_find_similar_live():
|
|
80
|
-
exa = Exa(API_KEY)
|
|
81
|
-
resp = exa.find_similar("https://www.openai.com", num_results=1)
|
|
82
|
-
assert resp.results
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
86
|
-
def test_get_contents_sync_live():
|
|
87
|
-
exa = Exa(API_KEY)
|
|
88
|
-
resp = exa.get_contents(urls=["https://example.com"], text=True, livecrawl="never")
|
|
89
|
-
assert resp.results
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@pytest.mark.asyncio
|
|
93
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
94
|
-
async def test_get_contents_async_live():
|
|
95
|
-
ax = AsyncExa(API_KEY)
|
|
96
|
-
resp = await ax.get_contents(urls=["https://example.com"], text=True, livecrawl="never")
|
|
97
|
-
assert resp.results
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# researchTask endpoint is still beta; mark as xfail if 404 returned
|
|
101
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
102
|
-
@pytest.mark.xfail(strict=False)
|
|
103
|
-
def test_research_task_live():
|
|
104
|
-
exa = Exa(API_KEY)
|
|
105
|
-
schema = {"type": "object", "properties": {"answer": {"type": "string"}}, "required": ["answer"]}
|
|
106
|
-
resp = exa.researchTask(input_instructions="Return the string 'pong'", output_schema=schema)
|
|
107
|
-
assert resp.id
|
|
108
|
-
|
|
109
|
-
########################################
|
|
110
|
-
# Live tests for new context / statuses features
|
|
111
|
-
########################################
|
|
112
|
-
|
|
113
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
114
|
-
def test_search_and_contents_context_live():
|
|
115
|
-
"""search_and_contents with context=True should return non-empty context string."""
|
|
116
|
-
exa = Exa(API_KEY)
|
|
117
|
-
resp = exa.search_and_contents("openai research", num_results=3, context=True, text=False)
|
|
118
|
-
assert resp.context is not None and isinstance(resp.context, str) and len(resp.context) > 0
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
122
|
-
def test_find_similar_and_contents_context_live():
|
|
123
|
-
"""find_similar_and_contents with context flag should include context string."""
|
|
124
|
-
exa = Exa(API_KEY)
|
|
125
|
-
resp = exa.find_similar_and_contents("https://www.openai.com", num_results=3, context=True, text=False)
|
|
126
|
-
# context may be empty depending on backend, but attribute should exist (None or str)
|
|
127
|
-
assert hasattr(resp, "context")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@pytest.mark.skipif(not _have_real_key(), reason="EXA_API_KEY not provided")
|
|
131
|
-
def test_get_contents_statuses_live():
|
|
132
|
-
"""get_contents should expose statuses list (possibly empty)."""
|
|
133
|
-
exa = Exa(API_KEY)
|
|
134
|
-
resp = exa.get_contents(urls=["https://techcrunch.com"], text=True, livecrawl="never")
|
|
135
|
-
# statuses attribute exists; ensure it's a list
|
|
136
|
-
assert isinstance(resp.statuses, list)
|