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
@@ -6,24 +6,37 @@
6
6
  import logging
7
7
  from typing import Any, Dict, List, Optional, Sequence
8
8
 
9
- from pydantic import BaseModel
10
- from dhisana.schemas.common import (SendEmailContext, QueryEmailContext, ReplyEmailContext)
11
-
9
+ from dhisana.schemas.common import (
10
+ SendEmailContext,
11
+ QueryEmailContext,
12
+ ReplyEmailContext,
13
+ )
12
14
  from dhisana.schemas.sales import MessageItem
13
-
14
15
  from dhisana.utils.google_workspace_tools import (
15
16
  send_email_using_service_account_async,
16
17
  list_emails_in_time_range_async,
17
- reply_to_email_async as gw_reply_to_email_async,
18
+ reply_to_email_async as gw_reply_to_email_async,
19
+ )
20
+ from dhisana.utils.google_oauth_tools import (
21
+ send_email_using_google_oauth_async,
22
+ list_emails_in_time_range_google_oauth_async,
23
+ reply_to_email_google_oauth_async,
24
+ )
25
+ from dhisana.utils.microsoft365_tools import (
26
+ send_email_using_microsoft_graph_async,
27
+ list_emails_in_time_range_m365_async,
28
+ reply_to_email_m365_async,
18
29
  )
19
30
  from dhisana.utils.smtp_email_tools import (
20
31
  send_email_via_smtp_async,
21
32
  list_emails_in_time_range_imap_async,
22
- reply_to_email_via_smtp_async,
33
+ reply_to_email_via_smtp_async,
23
34
  )
35
+ from dhisana.utils.mailgun_tools import send_email_using_mailgun_async
36
+ from dhisana.utils.sendgrid_tools import send_email_using_sendgrid_async
24
37
 
25
38
  # --------------------------------------------------------------------------- #
26
- # Provider selection helpers
39
+ # Provider-selection helpers
27
40
  # --------------------------------------------------------------------------- #
28
41
 
29
42
 
@@ -31,35 +44,35 @@ def _find_provider_cfg(
31
44
  tool_cfg: Optional[Sequence[Dict]], provider_name: str
32
45
  ) -> Optional[Dict]:
33
46
  """
34
- Return the *first* config dict whose ``name`` matches *provider_name*.
47
+ Return the *first* config-dict whose ``name`` matches *provider_name*.
35
48
  """
36
49
  if not tool_cfg:
37
50
  return None
38
51
  return next((c for c in tool_cfg if c.get("name") == provider_name), None)
39
52
 
40
53
 
41
- def _smtp_creds_for_sender(
42
- smtp_cfg: Dict, sender_email: str
43
- ) -> Optional[Dict[str, str]]:
54
+ def _smtp_creds_for_sender(smtp_cfg: Dict, sender_email: str) -> Optional[Dict[str, str]]:
44
55
  """
45
56
  Given an SMTP provider config and a sender address, return the matching
46
- ``username`` and ``password`` plus server settings, or ``None``.
57
+ ``username`` / ``password`` plus server settings, or ``None``.
47
58
  """
48
59
  try:
49
60
  usernames = [
50
- u.strip() for u in next(
51
- f for f in smtp_cfg["configuration"] if f["name"] == "usernames"
52
- )["value"].split(",")
61
+ u.strip()
62
+ for u in next(f for f in smtp_cfg["configuration"] if f["name"] == "usernames")[
63
+ "value"
64
+ ].split(",")
53
65
  if u.strip()
54
66
  ]
55
67
  passwords = [
56
- p.strip() for p in next(
57
- f for f in smtp_cfg["configuration"] if f["name"] == "passwords"
58
- )["value"].split(",")
68
+ p.strip()
69
+ for p in next(f for f in smtp_cfg["configuration"] if f["name"] == "passwords")[
70
+ "value"
71
+ ].split(",")
59
72
  ]
60
73
  if len(usernames) != len(passwords):
61
74
  logging.warning(
62
- "smtpEmail config: usernames/passwords length mismatch - skipping"
75
+ "smtpEmail config: usernames/passwords length mismatch skipping"
63
76
  )
64
77
  return None
65
78
 
@@ -97,17 +110,29 @@ async def send_email_async(
97
110
  send_email_context: SendEmailContext,
98
111
  tool_config: Optional[List[Dict]] = None,
99
112
  *,
100
- provider_order: Sequence[str] = ("smtpEmail", "googleworkspace"),
113
+ provider_order: Sequence[str] = (
114
+ "mailgun",
115
+ "sendgrid",
116
+ "google", # Google OAuth (per-user token)
117
+ "smtpEmail",
118
+ "googleworkspace", # Google Workspace service account (DWD)
119
+ "microsoft365",
120
+ ),
101
121
  ):
102
122
  """
103
123
  Send an e-mail using the first *configured* provider in *provider_order*.
104
124
 
105
125
  Returns whatever the underlying provider helper returns:
106
126
 
107
- * SMTP – ``Tuple[int, bytes]`` (status-code, server-msg)
108
- * Google Workspace – ``str`` (message id)
127
+ * SMTP str (Message-ID)
128
+ * Microsoft 365 → str (message-id)
129
+ * Google Workspace → str (message-id)
109
130
  """
131
+ # ------------------------------------------------------------------ #
132
+ # 1) Try the preferred providers in order
133
+ # ------------------------------------------------------------------ #
110
134
  for provider in provider_order:
135
+ # 1a) SMTP
111
136
  if provider == "smtpEmail":
112
137
  smtp_cfg = _find_provider_cfg(tool_config, "smtpEmail")
113
138
  if not smtp_cfg:
@@ -115,7 +140,7 @@ async def send_email_async(
115
140
 
116
141
  creds = _smtp_creds_for_sender(smtp_cfg, send_email_context.sender_email)
117
142
  if not creds:
118
- # No credentials for this sender; fall through to next provider.
143
+ # No creds for this sender fall through.
119
144
  continue
120
145
 
121
146
  return await send_email_via_smtp_async(
@@ -127,22 +152,104 @@ async def send_email_async(
127
152
  use_starttls=(creds["smtp_port"] == 587),
128
153
  )
129
154
 
155
+ # 1b) Mailgun
156
+ elif provider == "mailgun":
157
+ mg_cfg = _find_provider_cfg(tool_config, "mailgun")
158
+ if not mg_cfg:
159
+ continue
160
+ return await send_email_using_mailgun_async(send_email_context, tool_config)
161
+
162
+ # 1c) SendGrid
163
+ elif provider == "sendgrid":
164
+ sg_cfg = _find_provider_cfg(tool_config, "sendgrid")
165
+ if not sg_cfg:
166
+ continue
167
+ return await send_email_using_sendgrid_async(send_email_context, tool_config)
168
+
169
+ # 1d) Google (Gmail API via per-user OAuth)
170
+ elif provider == "google":
171
+ g_cfg = _find_provider_cfg(tool_config, "google")
172
+ if not g_cfg:
173
+ continue
174
+ return await send_email_using_google_oauth_async(send_email_context, tool_config)
175
+
176
+ # 1e) Google Workspace
130
177
  elif provider == "googleworkspace":
131
178
  gw_cfg = _find_provider_cfg(tool_config, "googleworkspace")
132
179
  if not gw_cfg:
133
180
  continue
134
- return await send_email_using_service_account_async(send_email_context, tool_config)
135
181
 
136
- # --- future providers can be added here ---
182
+ return await send_email_using_service_account_async(
183
+ send_email_context, tool_config
184
+ )
185
+
186
+ # 1f) Microsoft 365 (Graph API)
187
+ elif provider == "microsoft365":
188
+ ms_cfg = _find_provider_cfg(tool_config, "microsoft365")
189
+ if not ms_cfg:
190
+ continue
137
191
 
192
+ return await send_email_using_microsoft_graph_async(
193
+ send_email_context, tool_config
194
+ )
195
+
196
+ # -- future providers slot --------------------------------------
197
+
198
+ # ------------------------------------------------------------------ #
199
+ # 2) FINAL FALLBACK — use *first* SMTP credentials if available
200
+ # ------------------------------------------------------------------ #
201
+ smtp_cfg = _find_provider_cfg(tool_config, "smtpEmail")
202
+ if smtp_cfg:
203
+ try:
204
+ usernames = [
205
+ u.strip()
206
+ for u in next(
207
+ f for f in smtp_cfg["configuration"] if f["name"] == "usernames"
208
+ )["value"].split(",")
209
+ if u.strip()
210
+ ]
211
+ passwords = [
212
+ p.strip()
213
+ for p in next(
214
+ f for f in smtp_cfg["configuration"] if f["name"] == "passwords"
215
+ )["value"].split(",")
216
+ ]
217
+ if usernames and len(usernames) == len(passwords):
218
+ # Build a fake SendEmailContext for the fallback user, so that
219
+ # the underlying SMTP helper still sends the intended message
220
+ # but authenticates with the first available mailbox.
221
+ fallback_sender = usernames[0]
222
+ creds = _smtp_creds_for_sender(smtp_cfg, fallback_sender)
223
+
224
+ if creds:
225
+ logging.info(
226
+ "Fallback: no provider matched – using first SMTP creds (%s).",
227
+ creds["username"],
228
+ )
229
+ return await send_email_via_smtp_async(
230
+ send_email_context,
231
+ smtp_server=creds["smtp_host"],
232
+ smtp_port=creds["smtp_port"],
233
+ username=creds["username"],
234
+ password=creds["password"],
235
+ use_starttls=(creds["smtp_port"] == 587),
236
+ )
237
+ except Exception:
238
+ logging.exception("SMTP fallback failed")
239
+
240
+ # ------------------------------------------------------------------ #
241
+ # 3) Nothing worked
242
+ # ------------------------------------------------------------------ #
138
243
  raise RuntimeError("No suitable e-mail provider configured for this sender.")
139
244
 
140
245
 
246
+
247
+
141
248
  async def list_emails_async(
142
249
  query_email_context: QueryEmailContext,
143
250
  tool_config: Optional[List[Dict]] = None,
144
251
  *,
145
- provider_order: Sequence[str] = ("smtpEmail", "googleworkspace"),
252
+ provider_order: Sequence[str] = ("google", "smtpEmail", "googleworkspace", "microsoft365"),
146
253
  ) -> List[MessageItem]:
147
254
  """
148
255
  List e-mails (see ``QueryEmailContext``) using the first configured provider.
@@ -167,17 +274,31 @@ async def list_emails_async(
167
274
  password=creds["password"],
168
275
  )
169
276
 
277
+ elif provider == "google":
278
+ g_cfg = _find_provider_cfg(tool_config, "google")
279
+ if not g_cfg:
280
+ continue
281
+ return await list_emails_in_time_range_google_oauth_async(query_email_context, tool_config)
282
+
170
283
  elif provider == "googleworkspace":
171
284
  gw_cfg = _find_provider_cfg(tool_config, "googleworkspace")
172
285
  if not gw_cfg:
173
286
  continue
174
287
  return await list_emails_in_time_range_async(query_email_context, tool_config)
175
288
 
176
- # --- future providers go here ---
177
-
178
- raise RuntimeError("No suitable inbox provider configured for this sender.")
289
+ elif provider == "microsoft365":
290
+ ms_cfg = _find_provider_cfg(tool_config, "microsoft365")
291
+ if not ms_cfg:
292
+ continue
293
+ return await list_emails_in_time_range_m365_async(query_email_context, tool_config)
179
294
 
295
+ # --- future providers go here ---
180
296
 
297
+ logging.warning(
298
+ "No suitable inbox provider configured for sender %s; returning empty list.",
299
+ query_email_context.sender_email,
300
+ )
301
+ return []
181
302
 
182
303
 
183
304
  # ─────────────────────────────────────────────────────────────────────────────
@@ -187,7 +308,7 @@ async def reply_email_async(
187
308
  reply_email_context: ReplyEmailContext,
188
309
  tool_config: Optional[List[Dict]] = None,
189
310
  *,
190
- provider_order: Sequence[str] = ("smtpEmail", "googleworkspace"),
311
+ provider_order: Sequence[str] = ("google", "smtpEmail", "googleworkspace", "microsoft365"),
191
312
  ) -> Dict[str, Any]:
192
313
  """
193
314
  Reply (reply-all) to an e-mail using the first *configured* provider
@@ -197,16 +318,14 @@ async def reply_email_async(
197
318
  """
198
319
  for provider in provider_order:
199
320
  # ------------------------------------------------------------------
200
- # 1) SMTP / IMAP
321
+ # 1) SMTP
201
322
  # ------------------------------------------------------------------
202
323
  if provider == "smtpEmail":
203
324
  smtp_cfg = _find_provider_cfg(tool_config, "smtpEmail")
204
325
  if not smtp_cfg:
205
326
  continue
206
327
 
207
- creds = _smtp_creds_for_sender(
208
- smtp_cfg, reply_email_context.sender_email
209
- )
328
+ creds = _smtp_creds_for_sender(smtp_cfg, reply_email_context.sender_email)
210
329
  if not creds:
211
330
  continue
212
331
 
@@ -222,7 +341,17 @@ async def reply_email_async(
222
341
  )
223
342
 
224
343
  # ------------------------------------------------------------------
225
- # 2) Google Workspace service-account
344
+ # 2) Google OAuth (per-user)
345
+ # ------------------------------------------------------------------
346
+ elif provider == "google":
347
+ g_cfg = _find_provider_cfg(tool_config, "google")
348
+ if not g_cfg:
349
+ continue
350
+
351
+ return await reply_to_email_google_oauth_async(reply_email_context, tool_config)
352
+
353
+ # ------------------------------------------------------------------
354
+ # 3) Google Workspace service-account
226
355
  # ------------------------------------------------------------------
227
356
  elif provider == "googleworkspace":
228
357
  gw_cfg = _find_provider_cfg(tool_config, "googleworkspace")
@@ -231,6 +360,16 @@ async def reply_email_async(
231
360
 
232
361
  return await gw_reply_to_email_async(reply_email_context, tool_config)
233
362
 
363
+ # ------------------------------------------------------------------
364
+ # 4) Microsoft 365 (Graph)
365
+ # ------------------------------------------------------------------
366
+ elif provider == "microsoft365":
367
+ ms_cfg = _find_provider_cfg(tool_config, "microsoft365")
368
+ if not ms_cfg:
369
+ continue
370
+
371
+ return await reply_to_email_m365_async(reply_email_context, tool_config)
372
+
234
373
  # -- future providers slot -----------------------------------------
235
374
 
236
375
  raise RuntimeError("No suitable reply-capable e-mail provider configured.")