exa-py 1.14.12__tar.gz → 1.14.13__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.

Files changed (44) hide show
  1. {exa_py-1.14.12 → exa_py-1.14.13}/PKG-INFO +18 -16
  2. {exa_py-1.14.12 → exa_py-1.14.13}/README.md +1 -4
  3. exa_py-1.14.13/exa_py/websets/_generator/pydantic/BaseModel.jinja2 +42 -0
  4. {exa_py-1.14.12 → exa_py-1.14.13}/pyproject.toml +1 -1
  5. exa_py-1.14.12/exa_py.egg-info/PKG-INFO +0 -128
  6. exa_py-1.14.12/exa_py.egg-info/SOURCES.txt +0 -41
  7. exa_py-1.14.12/exa_py.egg-info/dependency_links.txt +0 -1
  8. exa_py-1.14.12/exa_py.egg-info/requires.txt +0 -5
  9. exa_py-1.14.12/exa_py.egg-info/top_level.txt +0 -1
  10. exa_py-1.14.12/setup.cfg +0 -4
  11. exa_py-1.14.12/setup.py +0 -26
  12. exa_py-1.14.12/tests/test_search_api.py +0 -136
  13. exa_py-1.14.12/tests/test_websets.py +0 -418
  14. exa_py-1.14.12/tests/test_websets_events.py +0 -416
  15. exa_py-1.14.12/tests/test_websets_imports.py +0 -439
  16. exa_py-1.14.12/tests/test_websets_monitors.py +0 -502
  17. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/__init__.py +0 -0
  18. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/api.py +0 -0
  19. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/py.typed +0 -0
  20. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/research/__init__.py +0 -0
  21. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/research/client.py +0 -0
  22. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/research/models.py +0 -0
  23. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/utils.py +0 -0
  24. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/__init__.py +0 -0
  25. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/client.py +0 -0
  26. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/core/__init__.py +0 -0
  27. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/core/base.py +0 -0
  28. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/enrichments/__init__.py +0 -0
  29. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/enrichments/client.py +0 -0
  30. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/events/__init__.py +0 -0
  31. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/events/client.py +0 -0
  32. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/imports/__init__.py +0 -0
  33. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/imports/client.py +0 -0
  34. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/items/__init__.py +0 -0
  35. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/items/client.py +0 -0
  36. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/monitors/__init__.py +0 -0
  37. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/monitors/client.py +0 -0
  38. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/monitors/runs/__init__.py +0 -0
  39. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/monitors/runs/client.py +0 -0
  40. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/searches/__init__.py +0 -0
  41. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/searches/client.py +0 -0
  42. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/types.py +0 -0
  43. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/webhooks/__init__.py +0 -0
  44. {exa_py-1.14.12 → exa_py-1.14.13}/exa_py/websets/webhooks/client.py +0 -0
@@ -1,27 +1,28 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.3
2
2
  Name: exa-py
3
- Version: 1.14.12
3
+ Version: 1.14.13
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
 
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
+ Exa API in Python
25
26
 
26
27
  ## Installation
27
28
 
@@ -126,3 +127,4 @@ exa = Exa(api_key="your-api-key")
126
127
  output_schema=OUTPUT_SCHEMA,
127
128
  )
128
129
  ```
130
+
@@ -1,9 +1,6 @@
1
1
  # Exa
2
2
 
3
- Exa (formerly Metaphor) API in Python
4
-
5
- Note: This API is basically the same as `metaphor-python` but reflects new
6
- features associated with Metaphor's rename to Exa. New site is https://exa.ai
3
+ Exa API in Python
7
4
 
8
5
  ## Installation
9
6
 
@@ -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 -%}
@@ -25,7 +25,7 @@ in-project = true
25
25
 
26
26
  [project]
27
27
  name = "exa-py"
28
- version = "1.14.12"
28
+ version = "1.14.13"
29
29
  description = "Python SDK for Exa API."
30
30
  readme = "README.md"
31
31
  requires-python = ">=3.9"
@@ -1,128 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: exa-py
3
- Version: 1.14.12
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
85
- response = exa.answer("This is a query to answer a question", text=True)
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,41 +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/events/__init__.py
24
- exa_py/websets/events/client.py
25
- exa_py/websets/imports/__init__.py
26
- exa_py/websets/imports/client.py
27
- exa_py/websets/items/__init__.py
28
- exa_py/websets/items/client.py
29
- exa_py/websets/monitors/__init__.py
30
- exa_py/websets/monitors/client.py
31
- exa_py/websets/monitors/runs/__init__.py
32
- exa_py/websets/monitors/runs/client.py
33
- exa_py/websets/searches/__init__.py
34
- exa_py/websets/searches/client.py
35
- exa_py/websets/webhooks/__init__.py
36
- exa_py/websets/webhooks/client.py
37
- tests/test_search_api.py
38
- tests/test_websets.py
39
- tests/test_websets_events.py
40
- tests/test_websets_imports.py
41
- tests/test_websets_monitors.py
@@ -1,5 +0,0 @@
1
- requests>=2.32.3
2
- typing-extensions>=4.12.2
3
- openai>=1.48
4
- pydantic>=2.10.6
5
- httpx>=0.28.1
@@ -1 +0,0 @@
1
- exa_py
exa_py-1.14.12/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
exa_py-1.14.12/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.12",
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)