dhisana 0.0.1.dev243__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.
- dhisana/__init__.py +1 -0
- dhisana/cli/__init__.py +1 -0
- dhisana/cli/cli.py +20 -0
- dhisana/cli/datasets.py +27 -0
- dhisana/cli/models.py +26 -0
- dhisana/cli/predictions.py +20 -0
- dhisana/schemas/__init__.py +1 -0
- dhisana/schemas/common.py +399 -0
- dhisana/schemas/sales.py +965 -0
- dhisana/ui/__init__.py +1 -0
- dhisana/ui/components.py +472 -0
- dhisana/utils/__init__.py +1 -0
- dhisana/utils/add_mapping.py +352 -0
- dhisana/utils/agent_tools.py +51 -0
- dhisana/utils/apollo_tools.py +1597 -0
- dhisana/utils/assistant_tool_tag.py +4 -0
- dhisana/utils/built_with_api_tools.py +282 -0
- dhisana/utils/cache_output_tools.py +98 -0
- dhisana/utils/cache_output_tools_local.py +78 -0
- dhisana/utils/check_email_validity_tools.py +717 -0
- dhisana/utils/check_for_intent_signal.py +107 -0
- dhisana/utils/check_linkedin_url_validity.py +209 -0
- dhisana/utils/clay_tools.py +43 -0
- dhisana/utils/clean_properties.py +135 -0
- dhisana/utils/company_utils.py +60 -0
- dhisana/utils/compose_salesnav_query.py +259 -0
- dhisana/utils/compose_search_query.py +759 -0
- dhisana/utils/compose_three_step_workflow.py +234 -0
- dhisana/utils/composite_tools.py +137 -0
- dhisana/utils/dataframe_tools.py +237 -0
- dhisana/utils/domain_parser.py +45 -0
- dhisana/utils/email_body_utils.py +72 -0
- dhisana/utils/email_parse_helpers.py +132 -0
- dhisana/utils/email_provider.py +375 -0
- dhisana/utils/enrich_lead_information.py +933 -0
- dhisana/utils/extract_email_content_for_llm.py +101 -0
- dhisana/utils/fetch_openai_config.py +129 -0
- dhisana/utils/field_validators.py +426 -0
- dhisana/utils/g2_tools.py +104 -0
- dhisana/utils/generate_content.py +41 -0
- dhisana/utils/generate_custom_message.py +271 -0
- dhisana/utils/generate_email.py +278 -0
- dhisana/utils/generate_email_response.py +465 -0
- dhisana/utils/generate_flow.py +102 -0
- dhisana/utils/generate_leads_salesnav.py +303 -0
- dhisana/utils/generate_linkedin_connect_message.py +224 -0
- dhisana/utils/generate_linkedin_response_message.py +317 -0
- dhisana/utils/generate_structured_output_internal.py +462 -0
- dhisana/utils/google_custom_search.py +267 -0
- dhisana/utils/google_oauth_tools.py +727 -0
- dhisana/utils/google_workspace_tools.py +1294 -0
- dhisana/utils/hubspot_clearbit.py +96 -0
- dhisana/utils/hubspot_crm_tools.py +2440 -0
- dhisana/utils/instantly_tools.py +149 -0
- dhisana/utils/linkedin_crawler.py +168 -0
- dhisana/utils/lusha_tools.py +333 -0
- dhisana/utils/mailgun_tools.py +156 -0
- dhisana/utils/mailreach_tools.py +123 -0
- dhisana/utils/microsoft365_tools.py +455 -0
- dhisana/utils/openai_assistant_and_file_utils.py +267 -0
- dhisana/utils/openai_helpers.py +977 -0
- dhisana/utils/openapi_spec_to_tools.py +45 -0
- dhisana/utils/openapi_tool/__init__.py +1 -0
- dhisana/utils/openapi_tool/api_models.py +633 -0
- dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +271 -0
- dhisana/utils/openapi_tool/openapi_tool.py +319 -0
- dhisana/utils/parse_linkedin_messages_txt.py +100 -0
- dhisana/utils/profile.py +37 -0
- dhisana/utils/proxy_curl_tools.py +1226 -0
- dhisana/utils/proxycurl_search_leads.py +426 -0
- dhisana/utils/python_function_to_tools.py +83 -0
- dhisana/utils/research_lead.py +176 -0
- dhisana/utils/sales_navigator_crawler.py +1103 -0
- dhisana/utils/salesforce_crm_tools.py +477 -0
- dhisana/utils/search_router.py +131 -0
- dhisana/utils/search_router_jobs.py +51 -0
- dhisana/utils/sendgrid_tools.py +162 -0
- dhisana/utils/serarch_router_local_business.py +75 -0
- dhisana/utils/serpapi_additional_tools.py +290 -0
- dhisana/utils/serpapi_google_jobs.py +117 -0
- dhisana/utils/serpapi_google_search.py +188 -0
- dhisana/utils/serpapi_local_business_search.py +129 -0
- dhisana/utils/serpapi_search_tools.py +852 -0
- dhisana/utils/serperdev_google_jobs.py +125 -0
- dhisana/utils/serperdev_local_business.py +154 -0
- dhisana/utils/serperdev_search.py +233 -0
- dhisana/utils/smtp_email_tools.py +582 -0
- dhisana/utils/test_connect.py +2087 -0
- dhisana/utils/trasform_json.py +173 -0
- dhisana/utils/web_download_parse_tools.py +189 -0
- dhisana/utils/workflow_code_model.py +5 -0
- dhisana/utils/zoominfo_tools.py +357 -0
- dhisana/workflow/__init__.py +1 -0
- dhisana/workflow/agent.py +18 -0
- dhisana/workflow/flow.py +44 -0
- dhisana/workflow/task.py +43 -0
- dhisana/workflow/test.py +90 -0
- dhisana-0.0.1.dev243.dist-info/METADATA +43 -0
- dhisana-0.0.1.dev243.dist-info/RECORD +102 -0
- dhisana-0.0.1.dev243.dist-info/WHEEL +5 -0
- dhisana-0.0.1.dev243.dist-info/entry_points.txt +2 -0
- dhisana-0.0.1.dev243.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, List, Dict
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
|
|
8
|
+
from dhisana.utils.assistant_tool_tag import assistant_tool
|
|
9
|
+
from dhisana.schemas.common import SendEmailContext
|
|
10
|
+
from dhisana.utils.email_body_utils import body_variants
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_mailgun_notify_key(tool_config: Optional[List[Dict]] = None) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Retrieve the Mailgun API key from tool_config or environment.
|
|
16
|
+
|
|
17
|
+
Looks for an integration named "mailgun" and reads configuration item
|
|
18
|
+
with name "apiKey". Falls back to env var MAILGUN_NOTIFY_KEY.
|
|
19
|
+
"""
|
|
20
|
+
key = None
|
|
21
|
+
if tool_config:
|
|
22
|
+
cfg = next((item for item in tool_config if item.get("name") == "mailgun"), None)
|
|
23
|
+
if cfg:
|
|
24
|
+
cfg_map = {i["name"]: i["value"] for i in cfg.get("configuration", []) if i}
|
|
25
|
+
key = cfg_map.get("apiKey")
|
|
26
|
+
key = key or os.getenv("MAILGUN_NOTIFY_KEY")
|
|
27
|
+
if not key:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"Mailgun integration is not configured. Please configure the connection to Mailgun in Integrations."
|
|
30
|
+
)
|
|
31
|
+
return key
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_mailgun_notify_domain(tool_config: Optional[List[Dict]] = None) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Retrieve the Mailgun domain from tool_config or environment.
|
|
37
|
+
|
|
38
|
+
Looks for an integration named "mailgun" and reads configuration item
|
|
39
|
+
with name "domain" (preferred) or legacy "notifyDomain".
|
|
40
|
+
Falls back to env var MAILGUN_DOMAIN, then MAILGUN_NOTIFY_DOMAIN.
|
|
41
|
+
"""
|
|
42
|
+
domain = None
|
|
43
|
+
if tool_config:
|
|
44
|
+
cfg = next((item for item in tool_config if item.get("name") == "mailgun"), None)
|
|
45
|
+
if cfg:
|
|
46
|
+
cfg_map = {i["name"]: i["value"] for i in cfg.get("configuration", []) if i}
|
|
47
|
+
domain = cfg_map.get("domain") or cfg_map.get("notifyDomain")
|
|
48
|
+
domain = domain or os.getenv("MAILGUN_DOMAIN") or os.getenv("MAILGUN_NOTIFY_DOMAIN")
|
|
49
|
+
if not domain:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"Mailgun integration is not configured. Please configure the connection to Mailgun in Integrations."
|
|
52
|
+
)
|
|
53
|
+
return domain
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@assistant_tool
|
|
57
|
+
async def send_email_with_mailgun(
|
|
58
|
+
sender: str,
|
|
59
|
+
recipients: List[str],
|
|
60
|
+
subject: str,
|
|
61
|
+
message: str,
|
|
62
|
+
tool_config: Optional[List[Dict]] = None,
|
|
63
|
+
body_format: Optional[str] = None,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Send an email using the Mailgun API.
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
- sender: Email address string, e.g. "Alice <alice@example.com>" or just address.
|
|
70
|
+
- recipients: List of recipient email addresses.
|
|
71
|
+
- subject: Subject string.
|
|
72
|
+
- message: HTML content body.
|
|
73
|
+
- tool_config: Optional integrations config list.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
api_key = get_mailgun_notify_key(tool_config)
|
|
77
|
+
domain = get_mailgun_notify_domain(tool_config)
|
|
78
|
+
|
|
79
|
+
body = message or ""
|
|
80
|
+
data = {
|
|
81
|
+
"from": sender,
|
|
82
|
+
"to": recipients,
|
|
83
|
+
"subject": subject,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
plain_body, html_body, _ = body_variants(body, body_format)
|
|
87
|
+
data["text"] = plain_body
|
|
88
|
+
data["html"] = html_body
|
|
89
|
+
|
|
90
|
+
async with aiohttp.ClientSession() as session:
|
|
91
|
+
async with session.post(
|
|
92
|
+
f"https://api.mailgun.net/v3/{domain}/messages",
|
|
93
|
+
auth=aiohttp.BasicAuth("api", api_key),
|
|
94
|
+
data=data,
|
|
95
|
+
) as response:
|
|
96
|
+
# Try to return JSON payload if available
|
|
97
|
+
try:
|
|
98
|
+
return await response.json()
|
|
99
|
+
except Exception:
|
|
100
|
+
return await response.text()
|
|
101
|
+
except Exception as ex:
|
|
102
|
+
logging.warning(f"Error sending email via Mailgun: {ex}")
|
|
103
|
+
return {"error": str(ex)}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def send_email_using_mailgun_async(
|
|
107
|
+
send_email_context: SendEmailContext,
|
|
108
|
+
tool_config: Optional[List[Dict]] = None,
|
|
109
|
+
) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Provider-style wrapper for Mailgun that accepts SendEmailContext and returns an id string.
|
|
112
|
+
"""
|
|
113
|
+
api_key = get_mailgun_notify_key(tool_config)
|
|
114
|
+
domain = get_mailgun_notify_domain(tool_config)
|
|
115
|
+
|
|
116
|
+
plain_body, html_body, _ = body_variants(
|
|
117
|
+
send_email_context.body,
|
|
118
|
+
getattr(send_email_context, "body_format", None),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
data = {
|
|
122
|
+
"from": f"{send_email_context.sender_name} <{send_email_context.sender_email}>",
|
|
123
|
+
"to": [send_email_context.recipient],
|
|
124
|
+
"subject": send_email_context.subject,
|
|
125
|
+
"text": plain_body,
|
|
126
|
+
"html": html_body,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
extra_headers = getattr(send_email_context, "headers", None) or {}
|
|
130
|
+
for header, value in extra_headers.items():
|
|
131
|
+
if not header or value is None:
|
|
132
|
+
continue
|
|
133
|
+
data[f"h:{header}"] = str(value)
|
|
134
|
+
|
|
135
|
+
async with aiohttp.ClientSession() as session:
|
|
136
|
+
async with session.post(
|
|
137
|
+
f"https://api.mailgun.net/v3/{domain}/messages",
|
|
138
|
+
auth=aiohttp.BasicAuth("api", api_key),
|
|
139
|
+
data=data,
|
|
140
|
+
) as response:
|
|
141
|
+
# Raise if not 2xx to match other providers' behavior
|
|
142
|
+
if response.status < 200 or response.status >= 300:
|
|
143
|
+
try:
|
|
144
|
+
detail = await response.text()
|
|
145
|
+
except Exception:
|
|
146
|
+
detail = f"status={response.status}"
|
|
147
|
+
raise RuntimeError(f"Mailgun send failed: {detail}")
|
|
148
|
+
try:
|
|
149
|
+
payload = await response.json()
|
|
150
|
+
except Exception:
|
|
151
|
+
payload = {"message": await response.text()}
|
|
152
|
+
|
|
153
|
+
# Normalise return value akin to other providers
|
|
154
|
+
msg_id = payload.get("id") if isinstance(payload, dict) else None
|
|
155
|
+
await asyncio.sleep(20)
|
|
156
|
+
return msg_id or str(payload)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import aiohttp
|
|
3
|
+
import logging
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
from dhisana.utils.assistant_tool_tag import assistant_tool
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.INFO)
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
base_url = 'https://api.mailreach.co/api/v1'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_mailreach_api_key(tool_config: Optional[List[Dict]] = None) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Retrieves the MailReach API key from the provided tool configuration or environment variables.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tool_config (list): A list of dictionaries containing the tool configuration.
|
|
19
|
+
Each dictionary should have a "name" key and a "configuration" key,
|
|
20
|
+
where "configuration" is a list of dictionaries containing "name" and "value" keys.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: The MailReach API key.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: If the MailReach integration has not been configured.
|
|
27
|
+
"""
|
|
28
|
+
api_key = None
|
|
29
|
+
|
|
30
|
+
if tool_config:
|
|
31
|
+
mailreach_config = next(
|
|
32
|
+
(item for item in tool_config if item.get("name") == "mailreach"), None
|
|
33
|
+
)
|
|
34
|
+
if mailreach_config:
|
|
35
|
+
config_map = {
|
|
36
|
+
item["name"]: item["value"]
|
|
37
|
+
for item in mailreach_config.get("configuration", [])
|
|
38
|
+
if item
|
|
39
|
+
}
|
|
40
|
+
api_key = config_map.get("apiKey")
|
|
41
|
+
|
|
42
|
+
api_key = api_key or os.getenv("MAILREACH_API_KEY")
|
|
43
|
+
|
|
44
|
+
if not api_key:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"MailReach integration is not configured. Please configure the connection to MailReach in Integrations."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return api_key
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_mailreach_headers(tool_config: Optional[List[Dict]] = None) -> Dict[str, str]:
|
|
53
|
+
"""
|
|
54
|
+
Get the headers required for MailReach API requests.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
tool_config (list): Optional tool configuration containing API credentials.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dict[str, str]: Headers dictionary with x-api-key and Content-Type.
|
|
61
|
+
"""
|
|
62
|
+
api_key = get_mailreach_api_key(tool_config)
|
|
63
|
+
headers = {
|
|
64
|
+
"x-api-key": api_key,
|
|
65
|
+
"Content-Type": "application/json"
|
|
66
|
+
}
|
|
67
|
+
return headers
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def _handle_mailreach_response(response: aiohttp.ClientResponse) -> Any:
|
|
71
|
+
"""
|
|
72
|
+
Handle MailReach API responses consistently.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
response: The aiohttp ClientResponse object.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The JSON response data.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
aiohttp.ClientResponseError: For rate limits or other errors.
|
|
82
|
+
"""
|
|
83
|
+
if response.status == 200:
|
|
84
|
+
return await response.json()
|
|
85
|
+
elif response.status == 429:
|
|
86
|
+
raise aiohttp.ClientResponseError(
|
|
87
|
+
request_info=response.request_info,
|
|
88
|
+
history=response.history,
|
|
89
|
+
status=response.status,
|
|
90
|
+
message="Rate limit exceeded",
|
|
91
|
+
headers=response.headers
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
error_message = await response.text()
|
|
95
|
+
logger.error(f"MailReach API Error {response.status}: {error_message}")
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@assistant_tool
|
|
100
|
+
async def ping_mailreach(
|
|
101
|
+
tool_config: Optional[List[Dict]] = None
|
|
102
|
+
) -> Dict[str, Any]:
|
|
103
|
+
"""
|
|
104
|
+
Ping the MailReach API to verify connectivity and authentication.
|
|
105
|
+
|
|
106
|
+
This is a simple endpoint to test if your API key is valid and the service is accessible.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
tool_config (list): Optional tool configuration containing API credentials.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Dict[str, Any]: Response from the ping endpoint, typically containing a success message.
|
|
113
|
+
"""
|
|
114
|
+
url = f"{base_url}/ping"
|
|
115
|
+
headers = get_mailreach_headers(tool_config)
|
|
116
|
+
|
|
117
|
+
logger.info("Pinging MailReach API...")
|
|
118
|
+
|
|
119
|
+
async with aiohttp.ClientSession() as session:
|
|
120
|
+
async with session.get(url, headers=headers) as response:
|
|
121
|
+
result = await _handle_mailreach_response(response)
|
|
122
|
+
logger.info("MailReach ping successful")
|
|
123
|
+
return result
|