dhisana 0.0.1.dev116__py3-none-any.whl → 0.0.1.dev236__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.
Files changed (69) hide show
  1. dhisana/schemas/common.py +10 -1
  2. dhisana/schemas/sales.py +203 -22
  3. dhisana/utils/add_mapping.py +0 -2
  4. dhisana/utils/apollo_tools.py +739 -119
  5. dhisana/utils/built_with_api_tools.py +4 -2
  6. dhisana/utils/check_email_validity_tools.py +35 -18
  7. dhisana/utils/check_for_intent_signal.py +1 -2
  8. dhisana/utils/check_linkedin_url_validity.py +34 -8
  9. dhisana/utils/clay_tools.py +3 -2
  10. dhisana/utils/clean_properties.py +1 -4
  11. dhisana/utils/compose_salesnav_query.py +0 -1
  12. dhisana/utils/compose_search_query.py +7 -3
  13. dhisana/utils/composite_tools.py +0 -1
  14. dhisana/utils/dataframe_tools.py +2 -2
  15. dhisana/utils/email_body_utils.py +72 -0
  16. dhisana/utils/email_provider.py +174 -35
  17. dhisana/utils/enrich_lead_information.py +183 -53
  18. dhisana/utils/fetch_openai_config.py +129 -0
  19. dhisana/utils/field_validators.py +1 -1
  20. dhisana/utils/g2_tools.py +0 -1
  21. dhisana/utils/generate_content.py +0 -1
  22. dhisana/utils/generate_email.py +68 -23
  23. dhisana/utils/generate_email_response.py +294 -46
  24. dhisana/utils/generate_flow.py +0 -1
  25. dhisana/utils/generate_linkedin_connect_message.py +9 -2
  26. dhisana/utils/generate_linkedin_response_message.py +137 -66
  27. dhisana/utils/generate_structured_output_internal.py +317 -164
  28. dhisana/utils/google_custom_search.py +150 -44
  29. dhisana/utils/google_oauth_tools.py +721 -0
  30. dhisana/utils/google_workspace_tools.py +278 -54
  31. dhisana/utils/hubspot_clearbit.py +3 -1
  32. dhisana/utils/hubspot_crm_tools.py +718 -272
  33. dhisana/utils/instantly_tools.py +3 -1
  34. dhisana/utils/lusha_tools.py +10 -7
  35. dhisana/utils/mailgun_tools.py +150 -0
  36. dhisana/utils/microsoft365_tools.py +447 -0
  37. dhisana/utils/openai_assistant_and_file_utils.py +121 -177
  38. dhisana/utils/openai_helpers.py +8 -6
  39. dhisana/utils/parse_linkedin_messages_txt.py +1 -3
  40. dhisana/utils/profile.py +37 -0
  41. dhisana/utils/proxy_curl_tools.py +377 -76
  42. dhisana/utils/proxycurl_search_leads.py +426 -0
  43. dhisana/utils/research_lead.py +3 -3
  44. dhisana/utils/sales_navigator_crawler.py +1 -6
  45. dhisana/utils/salesforce_crm_tools.py +323 -50
  46. dhisana/utils/search_router.py +131 -0
  47. dhisana/utils/search_router_jobs.py +51 -0
  48. dhisana/utils/sendgrid_tools.py +126 -91
  49. dhisana/utils/serarch_router_local_business.py +75 -0
  50. dhisana/utils/serpapi_additional_tools.py +290 -0
  51. dhisana/utils/serpapi_google_jobs.py +117 -0
  52. dhisana/utils/serpapi_google_search.py +188 -0
  53. dhisana/utils/serpapi_local_business_search.py +129 -0
  54. dhisana/utils/serpapi_search_tools.py +360 -432
  55. dhisana/utils/serperdev_google_jobs.py +125 -0
  56. dhisana/utils/serperdev_local_business.py +154 -0
  57. dhisana/utils/serperdev_search.py +233 -0
  58. dhisana/utils/smtp_email_tools.py +178 -18
  59. dhisana/utils/test_connect.py +1603 -130
  60. dhisana/utils/trasform_json.py +3 -3
  61. dhisana/utils/web_download_parse_tools.py +0 -1
  62. dhisana/utils/zoominfo_tools.py +2 -3
  63. dhisana/workflow/test.py +1 -1
  64. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/METADATA +1 -1
  65. dhisana-0.0.1.dev236.dist-info/RECORD +100 -0
  66. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/WHEEL +1 -1
  67. dhisana-0.0.1.dev116.dist-info/RECORD +0 -83
  68. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/entry_points.txt +0 -0
  69. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,66 @@ import json
3
3
  import logging
4
4
  import os
5
5
  import aiohttp
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import backoff
6
9
  from dhisana.utils.assistant_tool_tag import assistant_tool
7
10
  from dhisana.utils.cache_output_tools import retrieve_output, cache_output
8
- import backoff
9
11
 
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def get_google_custom_search_keys(tool_config: Optional[List[Dict]] = None) -> (str, str):
17
+ """
18
+ Retrieve the Google Custom Search API key (GOOGLE_SEARCH_KEY) and CX
19
+ from the provided tool_config or environment variables.
20
+
21
+ tool_config example:
22
+ [
23
+ {
24
+ "name": "google_custom_search",
25
+ "configuration": [
26
+ {"name": "apiKey", "value": "XXX"},
27
+ {"name": "cx", "value": "YYY"}
28
+ ]
29
+ }
30
+ ]
31
+
32
+ Returns:
33
+ (api_key, cx) tuple.
34
+
35
+ Raises:
36
+ ValueError: If the Google Custom Search integration has not been configured.
37
+ """
38
+ api_key = None
39
+ cx = None
40
+
41
+ if tool_config:
42
+ logger.debug("Looking for Google Custom Search config in tool_config.")
43
+ gcs_config = next((cfg for cfg in tool_config if cfg.get("name") == "google_custom_search"), None)
44
+ if gcs_config:
45
+ config_map = {
46
+ item["name"]: item["value"]
47
+ for item in gcs_config.get("configuration", [])
48
+ if item
49
+ }
50
+ api_key = config_map.get("apiKey")
51
+ cx = config_map.get("cx")
52
+ else:
53
+ logger.debug("No 'google_custom_search' config item found in tool_config.")
54
+
55
+ # Fall back to environment variables if not found in tool_config
56
+ api_key = api_key or os.environ.get('GOOGLE_SEARCH_KEY')
57
+ cx = cx or os.environ.get('GOOGLE_SEARCH_CX')
58
+
59
+ if not api_key or not cx:
60
+ raise ValueError(
61
+ "Google Custom Search integration is not configured. Please configure the connection to Google Custom Search in Integrations."
62
+ )
63
+
64
+ return api_key, cx
10
65
 
11
- # This is google Custom Search API (Not SERP API)
12
- # We use SERP API currently has it handles spell corrections etc correctly.
13
- # TODO: Enhance Custom search to handle corner cases. Its much lower cost than SERP API.
14
66
 
15
67
  @assistant_tool
16
68
  @backoff.on_exception(
@@ -20,62 +72,93 @@ import backoff
20
72
  giveup=lambda e: e.status != 429,
21
73
  factor=60,
22
74
  )
23
- async def search_google_custom(
75
+ async def search_google_custom_search(
24
76
  query: str,
25
- number_of_results: int = 10
26
- ):
77
+ number_of_results: int = 10,
78
+ offset: int = 0,
79
+ tool_config: Optional[List[Dict]] = None,
80
+ as_oq: Optional[str] = None
81
+ ) -> List[str]:
27
82
  """
28
- Search Google using the Google Custom Search JSON API and return the results as an array of serialized JSON strings.
29
-
83
+ Search Google using the Google Custom Search JSON API and return the results
84
+ as a list of JSON strings in SerpAPI-like format:
85
+ { "position", "title", "link", "snippet" }
86
+
30
87
  Parameters:
31
- - **query** (*str*): The search query.
32
- - **number_of_results** (*int*): The number of results to return.
88
+ - query (str): The search query.
89
+ - number_of_results (int): The number of results to return. Default 10.
90
+ - offset (int): The index offset for results (pagination). Default 0.
91
+ - tool_config (Optional[List[Dict]]): Tool config containing "google_custom_search" keys.
92
+ - as_oq (Optional[str]): Optional additional query terms, appended to 'query'.
93
+
94
+ Returns:
95
+ - List[str]: A list of JSON-serialized objects each containing title/link/snippet/position.
33
96
  """
97
+ logger.info("Entering search_google_custom_search")
34
98
 
35
- API_KEY = os.environ.get('GOOGLE_SEARCH_KEY')
36
- CX = os.environ.get('GOOGLE_SEARCH_CX') # Custom Search Engine ID
37
- if not API_KEY or not CX:
38
- return {'error': "Google Custom Search API key or CX not found in environment variables"}
99
+ # Construct the final query
100
+ full_query = query
101
+ if as_oq:
102
+ full_query += f" {as_oq}"
39
103
 
40
- url = "https://www.googleapis.com/customsearch/v1"
104
+ # Retrieve keys from tool_config or environment
105
+ try:
106
+ api_key, cx = get_google_custom_search_keys(tool_config)
107
+ except ValueError as e:
108
+ error_msg = str(e)
109
+ logger.error(error_msg)
110
+ return [json.dumps({"error": error_msg})]
111
+
112
+ # We apply pagination via "start" parameter in Google Custom Search
113
+ # "start" = 1 means the 1st result; offset means skip 'offset' results
114
+ # So if offset=0 => start=1, if offset=10 => start=11, etc.
115
+ start_index = offset + 1
41
116
 
117
+ url = "https://www.googleapis.com/customsearch/v1"
42
118
  params = {
43
- "key": API_KEY,
44
- "cx": CX,
45
- "q": query,
46
- "num": number_of_results
119
+ "key": api_key,
120
+ "cx": cx,
121
+ "q": full_query,
122
+ "num": number_of_results,
123
+ "start": start_index
47
124
  }
48
125
 
126
+ cache_key = f"{full_query}_{number_of_results}_{offset}"
127
+ cached_response = retrieve_output("search_google_custom_search", cache_key)
128
+ if cached_response is not None:
129
+ logger.info("Cache hit for search_google_custom_search.")
130
+ return cached_response
131
+
49
132
  try:
50
- cached_response = retrieve_output("search_google_custom", query)
51
- if cached_response is not None:
52
- return cached_response
53
-
54
133
  async with aiohttp.ClientSession() as session:
55
134
  async with session.get(url, params=params) as response:
56
135
  if response.status == 200:
57
136
  result = await response.json()
58
- serialized_results = []
59
-
60
- # Check for spelling corrections in the response
137
+
138
+ # Check for spelling suggestion
61
139
  if "spelling" in result and "correctedQuery" in result["spelling"]:
62
140
  corrected_query = result["spelling"]["correctedQuery"]
63
- logging.warning(f"Spelling suggestion detected: {corrected_query}. Retrying with original query.")
64
-
65
- # Re-run query using original terms
66
- params["q"] = query # Explicitly force the original query
67
- params["spell"] = query
141
+ logger.warning(f"Spelling suggestion detected ({corrected_query}). Retrying with original query.")
142
+ # Force original query (no changes).
143
+ params["q"] = full_query
68
144
  async with session.get(url, params=params) as retry_response:
69
145
  if retry_response.status == 200:
70
146
  retry_result = await retry_response.json()
71
- serialized_results = [json.dumps(item) for item in retry_result.get('items', [])]
147
+ normalized_results = _normalize_google_items(retry_result.get('items', []))
148
+ else:
149
+ retry_content = await retry_response.text()
150
+ return [json.dumps({"error": retry_content})]
72
151
  else:
73
- serialized_results = [json.dumps(item) for item in result.get('items', [])]
74
- cached_response = cache_output("search_google_custom", query, serialized_results)
75
- await asyncio.sleep(1) # Wait for 3 seconds before sending the response
152
+ # Normal path
153
+ normalized_results = _normalize_google_items(result.get('items', []))
154
+
155
+ serialized_results = [json.dumps(item) for item in normalized_results]
156
+ cache_output("search_google_custom_search", cache_key, serialized_results)
157
+ await asyncio.sleep(1) # small delay (optional)
76
158
  return serialized_results
159
+
77
160
  elif response.status == 429:
78
- logging.warning("search_google_custom Rate limit hit")
161
+ logger.warning("search_google_custom_search: Rate limit (429).")
79
162
  raise aiohttp.ClientResponseError(
80
163
  request_info=response.request_info,
81
164
  history=response.history,
@@ -84,13 +167,34 @@ async def search_google_custom(
84
167
  headers=response.headers
85
168
  )
86
169
  else:
87
- result = await response.json()
88
- logging.warning(f"search_google_custom Failed to run assistant: {result}")
89
- return {'error': result}
90
- except aiohttp.ClientResponseError as e:
91
- raise # Allow backoff to handle this exception
170
+ error_json = await response.json()
171
+ logger.warning(f"search_google_custom_search request failed: {error_json}")
172
+ return [json.dumps({"error": error_json})]
173
+
174
+ except aiohttp.ClientResponseError:
175
+ # Let backoff handle the re-raise
176
+ raise
92
177
  except Exception as e:
93
- return {'error': str(e)}
178
+ logger.exception("Exception during search_google_custom_search.")
179
+ return [json.dumps({"error": str(e)})]
180
+
181
+
182
+ def _normalize_google_items(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
183
+ """
184
+ Convert Google Custom Search 'items' into SerpAPI/Serper.dev-like format:
185
+ { position, title, link, snippet }
186
+ """
187
+ normalized = []
188
+ for i, item in enumerate(items):
189
+ normalized_item = {
190
+ "position": i + 1,
191
+ "title": item.get("title", ""),
192
+ "link": item.get("link", ""),
193
+ "snippet": item.get("snippet", "")
194
+ }
195
+ normalized.append(normalized_item)
196
+ return normalized
197
+
94
198
 
95
199
  @assistant_tool
96
200
  async def search_google_places(
@@ -108,7 +212,9 @@ async def search_google_places(
108
212
  """
109
213
  GOOGLE_SEARCH_KEY = os.environ.get('GOOGLE_SEARCH_KEY')
110
214
  if not GOOGLE_SEARCH_KEY:
111
- return {'error': "Google Places API key not found in environment variables"}
215
+ return {
216
+ 'error': "Google Places integration is not configured. Please configure the connection to Google Places in Integrations."
217
+ }
112
218
 
113
219
  url = "https://places.googleapis.com/v1/places:searchText"
114
220
  headers = {