tavily-python 0.7.23__tar.gz → 0.7.24__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.
Files changed (25) hide show
  1. {tavily_python-0.7.23 → tavily_python-0.7.24}/PKG-INFO +24 -1
  2. {tavily_python-0.7.23 → tavily_python-0.7.24}/README.md +23 -0
  3. {tavily_python-0.7.23 → tavily_python-0.7.24}/setup.py +1 -1
  4. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/async_tavily.py +36 -7
  5. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/tavily.py +50 -12
  6. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily_python.egg-info/PKG-INFO +24 -1
  7. {tavily_python-0.7.23 → tavily_python-0.7.24}/LICENSE +0 -0
  8. {tavily_python-0.7.23 → tavily_python-0.7.24}/setup.cfg +0 -0
  9. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/__init__.py +0 -0
  10. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/config.py +0 -0
  11. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/errors.py +0 -0
  12. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/hybrid_rag/__init__.py +0 -0
  13. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/hybrid_rag/hybrid_rag.py +0 -0
  14. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily/utils.py +0 -0
  15. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily_python.egg-info/SOURCES.txt +0 -0
  16. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily_python.egg-info/dependency_links.txt +0 -0
  17. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily_python.egg-info/requires.txt +0 -0
  18. {tavily_python-0.7.23 → tavily_python-0.7.24}/tavily_python.egg-info/top_level.txt +0 -0
  19. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_crawl.py +0 -0
  20. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_custom_session.py +0 -0
  21. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_errors.py +0 -0
  22. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_map.py +0 -0
  23. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_research.py +0 -0
  24. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_search.py +0 -0
  25. {tavily_python-0.7.23 → tavily_python-0.7.24}/tests/test_session_pooling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tavily-python
3
- Version: 0.7.23
3
+ Version: 0.7.24
4
4
  Summary: Python wrapper for the Tavily API
5
5
  Home-page: https://github.com/tavily-ai/tavily-python
6
6
  Author: Tavily AI
@@ -320,6 +320,29 @@ response = await client.search("latest AI research")
320
320
  - Custom session proxies take precedence over SDK proxy settings
321
321
  - The SDK will **not** close externally-provided sessions — you manage the lifecycle
322
322
 
323
+ ## Session & User Tracking
324
+
325
+ `session_id`, `human_id`, and `client_name` are optional identifiers that help attribute requests to a logical session, an end user, and a named client. All three are sent as HTTP headers (`X-Session-Id`, `X-Human-Id`, `X-Client-Name`) and are never persisted in raw form — `human_id` is hashed server-side.
326
+
327
+ Set them once at client init, or per-call (per-call wins):
328
+
329
+ ```python
330
+ from tavily import TavilyClient
331
+
332
+ # Client-level — applied to every request
333
+ client = TavilyClient(
334
+ api_key="tvly-YOUR_API_KEY",
335
+ session_id="my-session-123",
336
+ human_id="internal-user-id-42",
337
+ client_name="my-app",
338
+ )
339
+
340
+ # Per-call override
341
+ client.search("hello", session_id="ad-hoc-session")
342
+ ```
343
+
344
+ All three are opt-in. Leave them unset and the SDK sends nothing — behavior is identical to earlier versions.
345
+
323
346
  ## Documentation
324
347
 
325
348
  For a complete guide on how to use the different endpoints and their parameters, please head to our [Python API Reference](https://docs.tavily.com/sdk/python/reference).
@@ -293,6 +293,29 @@ response = await client.search("latest AI research")
293
293
  - Custom session proxies take precedence over SDK proxy settings
294
294
  - The SDK will **not** close externally-provided sessions — you manage the lifecycle
295
295
 
296
+ ## Session & User Tracking
297
+
298
+ `session_id`, `human_id`, and `client_name` are optional identifiers that help attribute requests to a logical session, an end user, and a named client. All three are sent as HTTP headers (`X-Session-Id`, `X-Human-Id`, `X-Client-Name`) and are never persisted in raw form — `human_id` is hashed server-side.
299
+
300
+ Set them once at client init, or per-call (per-call wins):
301
+
302
+ ```python
303
+ from tavily import TavilyClient
304
+
305
+ # Client-level — applied to every request
306
+ client = TavilyClient(
307
+ api_key="tvly-YOUR_API_KEY",
308
+ session_id="my-session-123",
309
+ human_id="internal-user-id-42",
310
+ client_name="my-app",
311
+ )
312
+
313
+ # Per-call override
314
+ client.search("hello", session_id="ad-hoc-session")
315
+ ```
316
+
317
+ All three are opt-in. Leave them unset and the SDK sends nothing — behavior is identical to earlier versions.
318
+
296
319
  ## Documentation
297
320
 
298
321
  For a complete guide on how to use the different endpoints and their parameters, please head to our [Python API Reference](https://docs.tavily.com/sdk/python/reference).
@@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
5
5
 
6
6
  setup(
7
7
  name='tavily-python',
8
- version='0.7.23',
8
+ version='0.7.24',
9
9
  url='https://github.com/tavily-ai/tavily-python',
10
10
  author='Tavily AI',
11
11
  author_email='support@tavily.com',
@@ -20,6 +20,9 @@ class AsyncTavilyClient:
20
20
  api_base_url: Optional[str] = None,
21
21
  client_source: Optional[str] = None,
22
22
  project_id: Optional[str] = None,
23
+ session_id: Optional[str] = None,
24
+ human_id: Optional[str] = None,
25
+ client_name: Optional[str] = None,
23
26
  client: Optional[httpx.AsyncClient] = None):
24
27
  if api_key is None:
25
28
  api_key = os.getenv("TAVILY_API_KEY")
@@ -36,7 +39,10 @@ class AsyncTavilyClient:
36
39
  "Content-Type": "application/json",
37
40
  **({"Authorization": f"Bearer {api_key}"} if api_key else {}),
38
41
  "X-Client-Source": client_source or "tavily-python",
39
- **({"X-Project-ID": tavily_project} if tavily_project else {})
42
+ **({"X-Project-ID": tavily_project} if tavily_project else {}),
43
+ **({"X-Session-Id": session_id} if session_id else {}),
44
+ **({"X-Human-Id": human_id} if human_id else {}),
45
+ **({"X-Client-Name": client_name} if client_name else {}),
40
46
  }
41
47
 
42
48
  self._external_client = client is not None
@@ -83,6 +89,23 @@ class AsyncTavilyClient:
83
89
  async def __aexit__(self, exc_type, exc_val, exc_tb):
84
90
  await self.close()
85
91
 
92
+ @staticmethod
93
+ def _pop_request_headers(kwargs: dict) -> Optional[dict]:
94
+ """Pop session_id, human_id, and client_name from kwargs and return them as headers.
95
+
96
+ Returns None when no overrides are provided so callers can omit the headers kwarg.
97
+ """
98
+ overrides = {}
99
+ for key, header_name in (
100
+ ("session_id", "X-Session-Id"),
101
+ ("human_id", "X-Human-Id"),
102
+ ("client_name", "X-Client-Name"),
103
+ ):
104
+ value = kwargs.pop(key, None)
105
+ if value is not None:
106
+ overrides[header_name] = str(value)
107
+ return overrides or None
108
+
86
109
  async def _search(
87
110
  self,
88
111
  query: str,
@@ -132,13 +155,14 @@ class AsyncTavilyClient:
132
155
 
133
156
  data = {k: v for k, v in data.items() if v is not None}
134
157
 
158
+ override_headers = self._pop_request_headers(kwargs)
135
159
  if kwargs:
136
160
  data.update(kwargs)
137
161
 
138
162
  timeout = min(timeout, 120)
139
163
 
140
164
  try:
141
- response = await self._client.post("/search", content=json.dumps(data), timeout=timeout)
165
+ response = await self._client.post("/search", content=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
142
166
  except httpx.TimeoutException:
143
167
  raise TimeoutError(timeout)
144
168
 
@@ -247,11 +271,12 @@ class AsyncTavilyClient:
247
271
 
248
272
  data = {k: v for k, v in data.items() if v is not None}
249
273
 
274
+ override_headers = self._pop_request_headers(kwargs)
250
275
  if kwargs:
251
276
  data.update(kwargs)
252
277
 
253
278
  try:
254
- response = await self._client.post("/extract", content=json.dumps(data), timeout=timeout)
279
+ response = await self._client.post("/extract", content=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
255
280
  except httpx.TimeoutException:
256
281
  raise TimeoutError(timeout)
257
282
 
@@ -355,13 +380,14 @@ class AsyncTavilyClient:
355
380
  "chunks_per_source": chunks_per_source,
356
381
  }
357
382
 
383
+ override_headers = self._pop_request_headers(kwargs)
358
384
  if kwargs:
359
385
  data.update(kwargs)
360
386
 
361
387
  data = {k: v for k, v in data.items() if v is not None}
362
388
 
363
389
  try:
364
- response = await self._client.post("/crawl", content=json.dumps(data), timeout=timeout)
390
+ response = await self._client.post("/crawl", content=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
365
391
  except httpx.TimeoutException:
366
392
  raise TimeoutError(timeout)
367
393
 
@@ -465,13 +491,14 @@ class AsyncTavilyClient:
465
491
  "include_usage": include_usage,
466
492
  }
467
493
 
494
+ override_headers = self._pop_request_headers(kwargs)
468
495
  if kwargs:
469
496
  data.update(kwargs)
470
497
 
471
498
  data = {k: v for k, v in data.items() if v is not None}
472
499
 
473
500
  try:
474
- response = await self._client.post("/map", content=json.dumps(data), timeout=timeout)
501
+ response = await self._client.post("/map", content=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
475
502
  except httpx.TimeoutException:
476
503
  raise TimeoutError(timeout)
477
504
 
@@ -659,6 +686,7 @@ class AsyncTavilyClient:
659
686
 
660
687
  data = {k: v for k, v in data.items() if v is not None}
661
688
 
689
+ override_headers = self._pop_request_headers(kwargs)
662
690
  if kwargs:
663
691
  data.update(kwargs)
664
692
 
@@ -669,7 +697,8 @@ class AsyncTavilyClient:
669
697
  "POST",
670
698
  "/research",
671
699
  content=json.dumps(data),
672
- timeout=timeout
700
+ timeout=timeout,
701
+ **({"headers": override_headers} if override_headers else {})
673
702
  ) as response:
674
703
  if response.status_code != 200:
675
704
  try:
@@ -701,7 +730,7 @@ class AsyncTavilyClient:
701
730
  else:
702
731
  async def _make_request():
703
732
  try:
704
- response = await self._client.post("/research", content=json.dumps(data), timeout=timeout)
733
+ response = await self._client.post("/research", content=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
705
734
  except httpx.TimeoutException:
706
735
  raise TimeoutError(timeout)
707
736
 
@@ -11,7 +11,18 @@ class TavilyClient:
11
11
  Tavily API client class.
12
12
  """
13
13
 
14
- def __init__(self, api_key: Optional[str] = None, proxies: Optional[dict[str, str]] = None, api_base_url: Optional[str] = None, client_source: Optional[str] = None, project_id: Optional[str] = None, session: Optional[requests.Session] = None):
14
+ def __init__(
15
+ self,
16
+ api_key: Optional[str] = None,
17
+ proxies: Optional[dict[str, str]] = None,
18
+ api_base_url: Optional[str] = None,
19
+ client_source: Optional[str] = None,
20
+ project_id: Optional[str] = None,
21
+ session_id: Optional[str] = None,
22
+ human_id: Optional[str] = None,
23
+ client_name: Optional[str] = None,
24
+ session: Optional[requests.Session] = None,
25
+ ):
15
26
  if api_key is None:
16
27
  api_key = os.getenv("TAVILY_API_KEY")
17
28
 
@@ -25,16 +36,19 @@ class TavilyClient:
25
36
 
26
37
  resolved_proxies = {k: v for k, v in resolved_proxies.items() if v} or None
27
38
  tavily_project = project_id or os.getenv("TAVILY_PROJECT")
28
-
39
+
29
40
  self.base_url = api_base_url or "https://api.tavily.com"
30
41
  self.api_key = api_key
31
42
  self.proxies = resolved_proxies
32
-
43
+
33
44
  self.headers = {
34
45
  "Content-Type": "application/json",
35
46
  **({"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}),
36
47
  "X-Client-Source": client_source or "tavily-python",
37
- **({"X-Project-ID": tavily_project} if tavily_project else {})
48
+ **({"X-Project-ID": tavily_project} if tavily_project else {}),
49
+ **({"X-Session-Id": session_id} if session_id else {}),
50
+ **({"X-Human-Id": human_id} if human_id else {}),
51
+ **({"X-Client-Name": client_name} if client_name else {}),
38
52
  }
39
53
 
40
54
  self._external_session = session is not None
@@ -59,6 +73,23 @@ class TavilyClient:
59
73
  def __exit__(self, exc_type, exc_val, exc_tb):
60
74
  self.close()
61
75
 
76
+ @staticmethod
77
+ def _pop_request_headers(kwargs: dict) -> Optional[dict]:
78
+ """Pop session_id, human_id, and client_name from kwargs and return them as headers.
79
+
80
+ Returns None when no overrides are provided so callers can omit the headers kwarg.
81
+ """
82
+ overrides = {}
83
+ for key, header_name in (
84
+ ("session_id", "X-Session-Id"),
85
+ ("human_id", "X-Human-Id"),
86
+ ("client_name", "X-Client-Name"),
87
+ ):
88
+ value = kwargs.pop(key, None)
89
+ if value is not None:
90
+ overrides[header_name] = str(value)
91
+ return overrides or None
92
+
62
93
  def _search(self,
63
94
  query: str,
64
95
  search_depth: Literal["basic", "advanced", "fast", "ultra-fast"] = None,
@@ -108,6 +139,7 @@ class TavilyClient:
108
139
 
109
140
  data = {k: v for k, v in data.items() if v is not None}
110
141
 
142
+ override_headers = self._pop_request_headers(kwargs)
111
143
  if kwargs:
112
144
  data.update(kwargs)
113
145
 
@@ -116,7 +148,7 @@ class TavilyClient:
116
148
  payload = json.dumps(data)
117
149
 
118
150
  try:
119
- response = self.session.post(url, data=payload, timeout=timeout)
151
+ response = self.session.post(url, data=payload, timeout=timeout, **({"headers": override_headers} if override_headers else {}))
120
152
  except requests.exceptions.Timeout:
121
153
  raise TimeoutError(timeout)
122
154
 
@@ -219,11 +251,12 @@ class TavilyClient:
219
251
 
220
252
  data = {k: v for k, v in data.items() if v is not None}
221
253
 
254
+ override_headers = self._pop_request_headers(kwargs)
222
255
  if kwargs:
223
256
  data.update(kwargs)
224
257
 
225
258
  try:
226
- response = self.session.post(self.base_url + "/extract", data=json.dumps(data), timeout=timeout)
259
+ response = self.session.post(self.base_url + "/extract", data=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
227
260
  except requests.exceptions.Timeout:
228
261
  raise TimeoutError(timeout)
229
262
 
@@ -320,13 +353,14 @@ class TavilyClient:
320
353
  "chunks_per_source": chunks_per_source,
321
354
  }
322
355
 
356
+ override_headers = self._pop_request_headers(kwargs)
323
357
  if kwargs:
324
358
  data.update(kwargs)
325
-
359
+
326
360
  data = {k: v for k, v in data.items() if v is not None}
327
361
 
328
362
  try:
329
- response = self.session.post(self.base_url + "/crawl", data=json.dumps(data), timeout=timeout)
363
+ response = self.session.post(self.base_url + "/crawl", data=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
330
364
  except requests.exceptions.Timeout:
331
365
  raise TimeoutError(timeout)
332
366
 
@@ -428,13 +462,14 @@ class TavilyClient:
428
462
  "include_usage": include_usage,
429
463
  }
430
464
 
465
+ override_headers = self._pop_request_headers(kwargs)
431
466
  if kwargs:
432
467
  data.update(kwargs)
433
-
468
+
434
469
  data = {k: v for k, v in data.items() if v is not None}
435
470
 
436
471
  try:
437
- response = self.session.post(self.base_url + "/map", data=json.dumps(data), timeout=timeout)
472
+ response = self.session.post(self.base_url + "/map", data=json.dumps(data), timeout=timeout, **({"headers": override_headers} if override_headers else {}))
438
473
  except requests.exceptions.Timeout:
439
474
  raise TimeoutError(timeout)
440
475
 
@@ -595,6 +630,7 @@ class TavilyClient:
595
630
 
596
631
  data = {k: v for k, v in data.items() if v is not None}
597
632
 
633
+ override_headers = self._pop_request_headers(kwargs)
598
634
  if kwargs:
599
635
  data.update(kwargs)
600
636
 
@@ -604,7 +640,8 @@ class TavilyClient:
604
640
  self.base_url + "/research",
605
641
  data=json.dumps(data),
606
642
  timeout=timeout,
607
- stream=True
643
+ stream=True,
644
+ **({"headers": override_headers} if override_headers else {})
608
645
  )
609
646
  except requests.exceptions.Timeout:
610
647
  raise TimeoutError(timeout)
@@ -641,7 +678,8 @@ class TavilyClient:
641
678
  response = self.session.post(
642
679
  self.base_url + "/research",
643
680
  data=json.dumps(data),
644
- timeout=timeout
681
+ timeout=timeout,
682
+ **({"headers": override_headers} if override_headers else {})
645
683
  )
646
684
  except requests.exceptions.Timeout:
647
685
  raise TimeoutError(timeout)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tavily-python
3
- Version: 0.7.23
3
+ Version: 0.7.24
4
4
  Summary: Python wrapper for the Tavily API
5
5
  Home-page: https://github.com/tavily-ai/tavily-python
6
6
  Author: Tavily AI
@@ -320,6 +320,29 @@ response = await client.search("latest AI research")
320
320
  - Custom session proxies take precedence over SDK proxy settings
321
321
  - The SDK will **not** close externally-provided sessions — you manage the lifecycle
322
322
 
323
+ ## Session & User Tracking
324
+
325
+ `session_id`, `human_id`, and `client_name` are optional identifiers that help attribute requests to a logical session, an end user, and a named client. All three are sent as HTTP headers (`X-Session-Id`, `X-Human-Id`, `X-Client-Name`) and are never persisted in raw form — `human_id` is hashed server-side.
326
+
327
+ Set them once at client init, or per-call (per-call wins):
328
+
329
+ ```python
330
+ from tavily import TavilyClient
331
+
332
+ # Client-level — applied to every request
333
+ client = TavilyClient(
334
+ api_key="tvly-YOUR_API_KEY",
335
+ session_id="my-session-123",
336
+ human_id="internal-user-id-42",
337
+ client_name="my-app",
338
+ )
339
+
340
+ # Per-call override
341
+ client.search("hello", session_id="ad-hoc-session")
342
+ ```
343
+
344
+ All three are opt-in. Leave them unset and the SDK sends nothing — behavior is identical to earlier versions.
345
+
323
346
  ## Documentation
324
347
 
325
348
  For a complete guide on how to use the different endpoints and their parameters, please head to our [Python API Reference](https://docs.tavily.com/sdk/python/reference).
File without changes
File without changes