dhisana 0.0.1.dev219__py3-none-any.whl → 0.0.1.dev221__py3-none-any.whl

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.
@@ -11,7 +11,7 @@ from dhisana.schemas.sales import LeadsQueryFilters, CompanyQueryFilters
11
11
  from dhisana.utils.cache_output_tools import cache_output, retrieve_output
12
12
  from dhisana.utils.assistant_tool_tag import assistant_tool
13
13
  from urllib.parse import urlparse, parse_qs
14
- from typing import Any, Dict, List, Optional, Union
14
+ from typing import Any, Dict, List, Optional, Tuple, Union
15
15
 
16
16
  from dhisana.utils.clean_properties import cleanup_properties
17
17
 
@@ -19,50 +19,81 @@ logging.basicConfig(level=logging.INFO)
19
19
  logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
- def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> str:
22
+ def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> Tuple[str, bool]:
23
23
  """
24
- Retrieves the APOLLO_API_KEY access token from the provided tool configuration.
24
+ Retrieves an Apollo access token from tool configuration or environment variables.
25
25
 
26
26
  Args:
27
- tool_config (list): A list of dictionaries containing the tool configuration.
28
- Each dictionary should have a "name" key and a "configuration" key,
29
- where "configuration" is a list of dictionaries containing "name" and "value" keys.
27
+ tool_config (list): Optional tool configuration payload provided to the tool.
30
28
 
31
29
  Returns:
32
- str: The APOLLO_API_KEY access token.
30
+ Tuple[str, bool]: A tuple containing the token string and a boolean flag indicating
31
+ whether the token represents an OAuth bearer token (``True``) or an API key (``False``).
33
32
 
34
33
  Raises:
35
34
  ValueError: If the Apollo integration has not been configured.
36
35
  """
37
- APOLLO_API_KEY = None
36
+ token: Optional[str] = None
37
+ is_oauth = False
38
38
 
39
39
  if tool_config:
40
- logger.debug(f"Tool config provided: {tool_config}")
41
40
  apollo_config = next(
42
41
  (item for item in tool_config if item.get("name") == "apollo"), None
43
42
  )
44
43
  if apollo_config:
45
44
  config_map = {
46
- item["name"]: item["value"]
45
+ item["name"]: item.get("value")
47
46
  for item in apollo_config.get("configuration", [])
48
47
  if item
49
48
  }
50
- APOLLO_API_KEY = config_map.get("apiKey")
49
+
50
+ raw_oauth = config_map.get("oauth_tokens")
51
+ if isinstance(raw_oauth, str):
52
+ try:
53
+ raw_oauth = json.loads(raw_oauth)
54
+ except Exception:
55
+ raw_oauth = None
56
+ if isinstance(raw_oauth, dict):
57
+ token = (
58
+ raw_oauth.get("access_token")
59
+ or raw_oauth.get("token")
60
+ )
61
+ if token:
62
+ is_oauth = True
63
+
64
+ if not token:
65
+ direct_access_token = config_map.get("access_token")
66
+ if direct_access_token:
67
+ token = direct_access_token
68
+ is_oauth = True
69
+
70
+ if not token:
71
+ api_key = config_map.get("apiKey") or config_map.get("api_key")
72
+ if api_key:
73
+ token = api_key
74
+ is_oauth = False
51
75
  else:
52
76
  logger.warning("No 'apollo' config item found in tool_config.")
53
- else:
54
- logger.debug("No tool_config provided or it's None.")
55
77
 
56
- # Check environment variable if no key found yet
57
- APOLLO_API_KEY = APOLLO_API_KEY or os.getenv("APOLLO_API_KEY")
78
+ if not token:
79
+ env_oauth_token = os.getenv("APOLLO_ACCESS_TOKEN")
80
+ if env_oauth_token:
81
+ token = env_oauth_token
82
+ is_oauth = True
58
83
 
59
- if not APOLLO_API_KEY:
84
+ if not token:
85
+ env_api_key = os.getenv("APOLLO_API_KEY")
86
+ if env_api_key:
87
+ token = env_api_key
88
+ is_oauth = False
89
+
90
+ if not token:
60
91
  logger.error("Apollo integration is not configured.")
61
92
  raise ValueError(
62
93
  "Apollo integration is not configured. Please configure the connection to Apollo in Integrations."
63
94
  )
64
95
 
65
- return APOLLO_API_KEY
96
+ return token, is_oauth
66
97
 
67
98
 
68
99
  @assistant_tool
@@ -94,16 +125,17 @@ async def enrich_person_info_from_apollo(
94
125
  """
95
126
  logger.info("Entering enrich_person_info_from_apollo")
96
127
 
97
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
128
+ token, is_oauth = get_apollo_access_token(tool_config)
98
129
 
99
130
  if not linkedin_url and not email and not phone:
100
131
  logger.warning("No linkedin_url, email, or phone provided. At least one is required.")
101
132
  return {'error': "At least one of linkedin_url, email, or phone must be provided"}
102
133
 
103
- headers = {
104
- "X-Api-Key": f"{APOLLO_API_KEY}",
105
- "Content-Type": "application/json"
106
- }
134
+ headers = {"Content-Type": "application/json"}
135
+ if is_oauth:
136
+ headers["Authorization"] = f"Bearer {token}"
137
+ else:
138
+ headers["X-Api-Key"] = token
107
139
 
108
140
  data = {}
109
141
  if linkedin_url:
@@ -186,11 +218,12 @@ async def lookup_person_in_apollo_by_name(
186
218
  logger.warning("No full_name provided.")
187
219
  return {'error': "Full name is required"}
188
220
 
189
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
190
- headers = {
191
- "X-Api-Key": f"{APOLLO_API_KEY}",
192
- "Content-Type": "application/json"
193
- }
221
+ token, is_oauth = get_apollo_access_token(tool_config)
222
+ headers = {"Content-Type": "application/json"}
223
+ if is_oauth:
224
+ headers["Authorization"] = f"Bearer {token}"
225
+ else:
226
+ headers["X-Api-Key"] = token
194
227
 
195
228
  # Construct the query payload
196
229
  data = {
@@ -263,18 +296,21 @@ async def enrich_organization_info_from_apollo(
263
296
  """
264
297
  logger.info("Entering enrich_organization_info_from_apollo")
265
298
 
266
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
299
+ token, is_oauth = get_apollo_access_token(tool_config)
267
300
 
268
301
  if not organization_domain:
269
302
  logger.warning("No organization domain provided.")
270
303
  return {'error': "organization domain must be provided"}
271
304
 
272
305
  headers = {
273
- "X-Api-Key": f"{APOLLO_API_KEY}",
274
306
  "Content-Type": "application/json",
275
307
  "Cache-Control": "no-cache",
276
308
  "accept": "application/json"
277
309
  }
310
+ if is_oauth:
311
+ headers["Authorization"] = f"Bearer {token}"
312
+ else:
313
+ headers["X-Api-Key"] = token
278
314
 
279
315
  cached_response = retrieve_output("enrich_organization_info_from_apollo", organization_domain)
280
316
  if cached_response is not None:
@@ -364,12 +400,15 @@ async def search_people_with_apollo(
364
400
  logger.warning("No payload given; returning empty result.")
365
401
  return []
366
402
 
367
- api_key = get_apollo_access_token(tool_config)
403
+ token, is_oauth = get_apollo_access_token(tool_config)
368
404
  headers = {
369
405
  "Cache-Control": "no-cache",
370
406
  "Content-Type": "application/json",
371
- "X-Api-Key": api_key,
372
407
  }
408
+ if is_oauth:
409
+ headers["Authorization"] = f"Bearer {token}"
410
+ else:
411
+ headers["X-Api-Key"] = token
373
412
 
374
413
  url = "https://api.apollo.io/api/v1/mixed_people/search"
375
414
  logger.info(f"Sending payload to Apollo (single page): {json.dumps(dynamic_payload, indent=2)}")
@@ -534,7 +573,7 @@ async def search_leads_with_apollo(
534
573
  # Important: handle personNotTitles as well
535
574
  "personNotTitles": "person_not_titles",
536
575
 
537
- "qOrganizationJobTitles": "q_keywords",
576
+ "qOrganizationJobTitles": "q_organization_job_titles",
538
577
  "sortAscending": "sort_ascending",
539
578
  "sortByField": "sort_by_field",
540
579
  "contactEmailStatusV2": "contact_email_status",
@@ -607,6 +646,8 @@ async def search_leads_with_apollo(
607
646
  "organization_ids",
608
647
  "organization_num_employees_ranges",
609
648
  "person_not_titles", # <--- added so single item is forced into list
649
+ "q_organization_job_titles",
650
+ "organization_latest_funding_stage_cd",
610
651
  ):
611
652
  if isinstance(final_value, str):
612
653
  final_value = [final_value]
@@ -636,6 +677,10 @@ async def search_leads_with_apollo(
636
677
  "page": 1,
637
678
  "per_page": min(max_items, 100),
638
679
  }
680
+ if query.job_openings_with_titles:
681
+ dynamic_payload["q_organization_job_titles"] = query.job_openings_with_titles
682
+ if query.latest_funding_stages:
683
+ dynamic_payload["organization_latest_funding_stage_cd"] = query.latest_funding_stages
639
684
  if query.sort_by_field is not None:
640
685
  dynamic_payload["sort_by_field"] = query.sort_by_field
641
686
  if query.sort_ascending is not None:
@@ -746,7 +791,7 @@ async def search_leads_with_apollo_page(
746
791
  "organizationNumEmployeesRanges": "organization_num_employees_ranges",
747
792
  "personTitles": "person_titles",
748
793
  "personNotTitles": "person_not_titles",
749
- "qOrganizationJobTitles": "q_keywords",
794
+ "qOrganizationJobTitles": "q_organization_job_titles",
750
795
  "sortAscending": "sort_ascending",
751
796
  "sortByField": "sort_by_field",
752
797
  "contactEmailStatusV2": "contact_email_status",
@@ -804,6 +849,8 @@ async def search_leads_with_apollo_page(
804
849
  "organization_ids",
805
850
  "organization_num_employees_ranges",
806
851
  "person_not_titles",
852
+ "q_organization_job_titles",
853
+ "organization_latest_funding_stage_cd",
807
854
  ):
808
855
  if isinstance(final_value, str):
809
856
  final_value = [final_value]
@@ -827,6 +874,10 @@ async def search_leads_with_apollo_page(
827
874
  or [f"{query.min_employees_in_organization or 1},{query.max_employees_in_organization or 1000}"]
828
875
  ),
829
876
  }
877
+ if query.job_openings_with_titles:
878
+ dynamic_payload["q_organization_job_titles"] = query.job_openings_with_titles
879
+ if query.latest_funding_stages:
880
+ dynamic_payload["organization_latest_funding_stage_cd"] = query.latest_funding_stages
830
881
  if query.sort_by_field is not None:
831
882
  dynamic_payload["sort_by_field"] = query.sort_by_field
832
883
  if query.sort_ascending is not None:
@@ -840,12 +891,15 @@ async def search_leads_with_apollo_page(
840
891
  f" Payload: {json.dumps(page_payload, indent=2)}")
841
892
 
842
893
  # Get the full Apollo API response with pagination metadata
843
- api_key = get_apollo_access_token(tool_config)
894
+ token, is_oauth = get_apollo_access_token(tool_config)
844
895
  headers = {
845
896
  "Cache-Control": "no-cache",
846
897
  "Content-Type": "application/json",
847
- "X-Api-Key": api_key,
848
898
  }
899
+ if is_oauth:
900
+ headers["Authorization"] = f"Bearer {token}"
901
+ else:
902
+ headers["X-Api-Key"] = token
849
903
 
850
904
  url = "https://api.apollo.io/api/v1/mixed_people/search"
851
905
 
@@ -950,17 +1004,20 @@ async def get_organization_details_from_apollo(
950
1004
  """
951
1005
  logger.info("Entering get_organization_details_from_apollo")
952
1006
 
953
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
1007
+ token, is_oauth = get_apollo_access_token(tool_config)
954
1008
  if not organization_id:
955
1009
  logger.warning("No organization_id provided.")
956
1010
  return {'error': "Organization ID must be provided"}
957
1011
 
958
1012
  headers = {
959
- "X-Api-Key": APOLLO_API_KEY,
960
1013
  "Content-Type": "application/json",
961
1014
  "Cache-Control": "no-cache",
962
1015
  "Accept": "application/json"
963
1016
  }
1017
+ if is_oauth:
1018
+ headers["Authorization"] = f"Bearer {token}"
1019
+ else:
1020
+ headers["X-Api-Key"] = token
964
1021
 
965
1022
  cached_response = retrieve_output("get_organization_details_from_apollo", organization_id)
966
1023
  if cached_response is not None:
@@ -1202,12 +1259,15 @@ async def search_companies_with_apollo(
1202
1259
  logger.warning("No payload given; returning empty result.")
1203
1260
  return []
1204
1261
 
1205
- api_key = get_apollo_access_token(tool_config)
1262
+ token, is_oauth = get_apollo_access_token(tool_config)
1206
1263
  headers = {
1207
1264
  "Cache-Control": "no-cache",
1208
1265
  "Content-Type": "application/json",
1209
- "X-Api-Key": api_key,
1210
1266
  }
1267
+ if is_oauth:
1268
+ headers["Authorization"] = f"Bearer {token}"
1269
+ else:
1270
+ headers["X-Api-Key"] = token
1211
1271
 
1212
1272
  url = "https://api.apollo.io/api/v1/organizations/search"
1213
1273
  logger.info(f"Sending payload to Apollo organizations endpoint (single page): {json.dumps(dynamic_payload, indent=2)}")
@@ -1508,12 +1568,15 @@ async def search_companies_with_apollo_page(
1508
1568
  f" Payload: {json.dumps(cleaned_payload, indent=2)}")
1509
1569
 
1510
1570
  # Get the full Apollo API response with pagination metadata
1511
- api_key = get_apollo_access_token(tool_config)
1571
+ token, is_oauth = get_apollo_access_token(tool_config)
1512
1572
  headers = {
1513
1573
  "Cache-Control": "no-cache",
1514
1574
  "Content-Type": "application/json",
1515
- "X-Api-Key": api_key,
1516
1575
  }
1576
+ if is_oauth:
1577
+ headers["Authorization"] = f"Bearer {token}"
1578
+ else:
1579
+ headers["X-Api-Key"] = token
1517
1580
 
1518
1581
  url = "https://api.apollo.io/api/v1/organizations/search"
1519
1582
 
@@ -296,6 +296,68 @@ async def test_proxycurl(api_key: str) -> Dict[str, Any]:
296
296
  return {"success": False, "status_code": 0, "error_message": str(e)}
297
297
 
298
298
 
299
+ async def test_exa(api_key: str) -> Dict[str, Any]:
300
+ """Verify Exa connectivity by issuing a minimal search request."""
301
+
302
+ url = "https://api.exa.ai/search"
303
+ headers = {
304
+ "x-api-key": api_key,
305
+ "Content-Type": "application/json",
306
+ "Accept": "application/json",
307
+ }
308
+ payload = {
309
+ "query": "Dhisana connectivity check",
310
+ "numResults": 1,
311
+ }
312
+
313
+ try:
314
+ timeout = aiohttp.ClientTimeout(total=10)
315
+ async with aiohttp.ClientSession(timeout=timeout) as session:
316
+ async with session.post(url, headers=headers, json=payload) as response:
317
+ status = response.status
318
+ data = await safe_json(response)
319
+
320
+ if status != 200:
321
+ err_message = None
322
+ if isinstance(data, dict):
323
+ err_message = (
324
+ data.get("message")
325
+ or data.get("error")
326
+ or data.get("detail")
327
+ )
328
+ if isinstance(err_message, dict):
329
+ err_message = err_message.get("message") or str(err_message)
330
+ return {
331
+ "success": False,
332
+ "status_code": status,
333
+ "error_message": err_message or f"Non-200 from Exa: {status}",
334
+ }
335
+
336
+ if isinstance(data, dict):
337
+ if "error" in data:
338
+ error_value = data["error"]
339
+ if isinstance(error_value, dict):
340
+ error_value = error_value.get("message") or str(error_value)
341
+ return {
342
+ "success": False,
343
+ "status_code": status,
344
+ "error_message": str(error_value),
345
+ }
346
+
347
+ results = data.get("results")
348
+ if isinstance(results, list):
349
+ return {"success": True, "status_code": status, "error_message": None}
350
+
351
+ return {
352
+ "success": False,
353
+ "status_code": status,
354
+ "error_message": "Unexpected response from Exa API.",
355
+ }
356
+ except Exception as exc:
357
+ logger.error(f"Exa test failed: {exc}")
358
+ return {"success": False, "status_code": 0, "error_message": str(exc)}
359
+
360
+
299
361
  async def test_apollo(api_key: str) -> Dict[str, Any]:
300
362
  organization_domain = 'microsoft.com'
301
363
  url = f'https://api.apollo.io/api/v1/organizations/enrich?domain={organization_domain}'
@@ -1179,6 +1241,7 @@ async def test_connectivity(tool_config: List[Dict[str, Any]]) -> Dict[str, Dict
1179
1241
  "serpapi": test_serpapi,
1180
1242
  "serperdev": test_serperdev,
1181
1243
  "proxycurl": test_proxycurl,
1244
+ "exa": test_exa,
1182
1245
  "apollo": test_apollo,
1183
1246
  "hubspot": test_hubspot,
1184
1247
  "github": test_github,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev219
3
+ Version: 0.0.1.dev221
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -12,7 +12,7 @@ dhisana/ui/components.py,sha256=4NXrAyl9tx2wWwoVYyABO-EOGnreGMvql1AkXWajIIo,1431
12
12
  dhisana/utils/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
13
13
  dhisana/utils/add_mapping.py,sha256=oq_QNqag86DhgdwINBRRXNx7SOb8Q9M-V0QLP6pTzr8,13837
14
14
  dhisana/utils/agent_tools.py,sha256=pzBFvfhU4wfSB4zv1eiRzjmnteJnfhC5V32r_v1m38Y,2321
15
- dhisana/utils/apollo_tools.py,sha256=no-PwDVtPfOSDr6eZ-cN5C3C_Yf2SeElft5ksap24Gg,64136
15
+ dhisana/utils/apollo_tools.py,sha256=aOJYxFlSWaFa_uCt0ZTDEvX6qZTrnAO5NrZgDBYWPN4,66506
16
16
  dhisana/utils/assistant_tool_tag.py,sha256=rYRl8ubLI7fUUIjg30XTefHBkFgRqNEVC12lF6U6Z-8,119
17
17
  dhisana/utils/built_with_api_tools.py,sha256=TFNGhnPb2vFdveVCpjiCvE1WKe_eK95UPpR0Ha5NgMQ,10260
18
18
  dhisana/utils/cache_output_tools.py,sha256=sSAruvUZn-WAJQ0lB9T1QjSmkm-_14AuxC9xKmcCQ0k,3428
@@ -78,7 +78,7 @@ dhisana/utils/serperdev_google_jobs.py,sha256=m5_2f_5y79FOFZz1A_go6m0hIUfbbAoZ0Y
78
78
  dhisana/utils/serperdev_local_business.py,sha256=JoZfTg58Hojv61cyuwA2lcnPdLT1lawnWaBNrUYWnuQ,6447
79
79
  dhisana/utils/serperdev_search.py,sha256=_iBKIfHMq4gFv5StYz58eArriygoi1zW6VnLlux8vto,9363
80
80
  dhisana/utils/smtp_email_tools.py,sha256=kx0VSa0JQyVLD020oARCHhOBZJxDwMIri1Z7jPN0nP4,15843
81
- dhisana/utils/test_connect.py,sha256=1rupW6dIfRzBKUH1AcWqfhYWhW-wETD475AJlCbgvFw,58254
81
+ dhisana/utils/test_connect.py,sha256=-5Wxc1D4KyCuNJjf9UvIXqK-eXmcr_1MpUg4u_B06SQ,60749
82
82
  dhisana/utils/trasform_json.py,sha256=s48DoyzVVCI4dTvSUwF5X-exX6VH6nPWrjbUENkYuNE,6979
83
83
  dhisana/utils/web_download_parse_tools.py,sha256=ouXwH7CmjcRjoBfP5BWat86MvcGO-8rLCmWQe_eZKjc,7810
84
84
  dhisana/utils/workflow_code_model.py,sha256=YPWse5vBb3O6Km2PvKh1Q3AB8qBkzLt1CrR5xOL9Mro,99
@@ -92,8 +92,8 @@ dhisana/workflow/agent.py,sha256=esv7_i_XuMkV2j1nz_UlsHov_m6X5WZZiZm_tG4OBHU,565
92
92
  dhisana/workflow/flow.py,sha256=xWE3qQbM7j2B3FH8XnY3zOL_QXX4LbTW4ArndnEYJE0,1638
93
93
  dhisana/workflow/task.py,sha256=HlWz9mtrwLYByoSnePOemBUBrMEcj7KbgNjEE1oF5wo,1830
94
94
  dhisana/workflow/test.py,sha256=kwW8jWqSBNcRmoyaxlTuZCMOpGJpTbJQgHI7gSjwdzM,3399
95
- dhisana-0.0.1.dev219.dist-info/METADATA,sha256=T-dWWhSYokCE-P7SB5slEkbuuHfXlpm7lrWGRXiSaV0,1190
96
- dhisana-0.0.1.dev219.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
97
- dhisana-0.0.1.dev219.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
98
- dhisana-0.0.1.dev219.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
99
- dhisana-0.0.1.dev219.dist-info/RECORD,,
95
+ dhisana-0.0.1.dev221.dist-info/METADATA,sha256=b-NJrLnqGwpHzBkbkln7ZoEUSbUG9-48gIwjJiicSxE,1190
96
+ dhisana-0.0.1.dev221.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
97
+ dhisana-0.0.1.dev221.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
98
+ dhisana-0.0.1.dev221.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
99
+ dhisana-0.0.1.dev221.dist-info/RECORD,,