dhisana 0.0.1.dev238__tar.gz → 0.0.1.dev240__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.
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/setup.py +1 -1
- dhisana-0.0.1.dev240/src/dhisana/utils/mailreach_tools.py +123 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/test_connect.py +70 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana.egg-info/PKG-INFO +1 -1
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana.egg-info/SOURCES.txt +2 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_connectivity.py +16 -0
- dhisana-0.0.1.dev240/tests/test_mailreach.py +179 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/README.md +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/setup.cfg +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/schemas/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/schemas/common.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/schemas/sales.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/ui/components.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/add_mapping.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/agent_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/apollo_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/assistant_tool_tag.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/built_with_api_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/cache_output_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/cache_output_tools_local.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/check_email_validity_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/check_for_intent_signal.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/clay_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/clean_properties.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/company_utils.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/compose_salesnav_query.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/compose_search_query.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/composite_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/dataframe_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/domain_parser.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/email_body_utils.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/email_parse_helpers.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/email_provider.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/enrich_lead_information.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/fetch_openai_config.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/field_validators.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/g2_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_content.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_email.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_email_response.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_flow.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/google_custom_search.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/google_oauth_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/google_workspace_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/hubspot_clearbit.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/instantly_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/linkedin_crawler.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/lusha_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/mailgun_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/microsoft365_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openai_helpers.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/profile.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/proxy_curl_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/python_function_to_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/research_lead.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/search_router.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/search_router_jobs.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/sendgrid_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serarch_router_local_business.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serpapi_google_search.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serpapi_search_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serperdev_local_business.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serperdev_search.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/smtp_email_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/trasform_json.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/web_download_parse_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/workflow_code_model.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/zoominfo_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/workflow/__init__.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/workflow/agent.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/workflow/flow.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/workflow/task.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/workflow/test.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_agent_tools.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_apollo_company_search.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_apollo_lead_search.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_email_body_utils.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_google_document.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_hubspot_call_logs.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_linkedin_serper.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_mcp_connectivity.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_proxycurl_get_company_search_id.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_proxycurl_job_count.py +0 -0
- {dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/tests/test_structured_output_with_mcp.py +0 -0
|
@@ -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
|
|
@@ -483,6 +483,31 @@ async def test_sendgrid(api_key: str) -> Dict[str, Any]:
|
|
|
483
483
|
return {"success": False, "status_code": 0, "error_message": str(e)}
|
|
484
484
|
|
|
485
485
|
|
|
486
|
+
async def test_mailreach(api_key: str) -> Dict[str, Any]:
|
|
487
|
+
"""
|
|
488
|
+
Basic MailReach connectivity check using the Ping endpoint.
|
|
489
|
+
|
|
490
|
+
Uses the /v1/ping endpoint to verify API key validity and service availability.
|
|
491
|
+
Reference: https://docs.mailreach.co/reference/getv1ping
|
|
492
|
+
"""
|
|
493
|
+
url = "https://api.mailreach.co/api/v1/ping"
|
|
494
|
+
headers = {"x-api-key": api_key}
|
|
495
|
+
try:
|
|
496
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
|
|
497
|
+
async with session.get(url, headers=headers) as response:
|
|
498
|
+
status = response.status
|
|
499
|
+
data = await safe_json(response)
|
|
500
|
+
if status != 200:
|
|
501
|
+
msg = None
|
|
502
|
+
if data and isinstance(data, dict):
|
|
503
|
+
msg = data.get("message") or data.get("error")
|
|
504
|
+
return {"success": False, "status_code": status, "error_message": msg or f"MailReach non-200: {status}"}
|
|
505
|
+
return {"success": True, "status_code": status, "error_message": None}
|
|
506
|
+
except Exception as e:
|
|
507
|
+
logger.error(f"MailReach test failed: {e}")
|
|
508
|
+
return {"success": False, "status_code": 0, "error_message": str(e)}
|
|
509
|
+
|
|
510
|
+
|
|
486
511
|
async def test_samgov(api_key: str) -> Dict[str, Any]:
|
|
487
512
|
"""Test SAM.gov connectivity by fetching a single opportunity."""
|
|
488
513
|
|
|
@@ -1646,6 +1671,49 @@ async def test_scarf(api_key: str) -> Dict[str, Any]:
|
|
|
1646
1671
|
return {"success": False, "status_code": 0, "error_message": str(exc)}
|
|
1647
1672
|
|
|
1648
1673
|
|
|
1674
|
+
async def test_theorg(api_key: str) -> Dict[str, Any]:
|
|
1675
|
+
"""Validate The Org API key by calling the Usage endpoint."""
|
|
1676
|
+
url = "https://api.theorg.com/v1.1/usage"
|
|
1677
|
+
headers = {
|
|
1678
|
+
"X-Api-Key": api_key,
|
|
1679
|
+
"Accept": "application/json",
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
try:
|
|
1683
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
1684
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
1685
|
+
async with session.get(url, headers=headers) as response:
|
|
1686
|
+
status = response.status
|
|
1687
|
+
data = await safe_json(response)
|
|
1688
|
+
|
|
1689
|
+
if status == 200:
|
|
1690
|
+
payload = data if isinstance(data, dict) else None
|
|
1691
|
+
usage = payload.get("data") if payload else None
|
|
1692
|
+
if isinstance(usage, dict):
|
|
1693
|
+
return {"success": True, "status_code": status, "error_message": None}
|
|
1694
|
+
return {
|
|
1695
|
+
"success": False,
|
|
1696
|
+
"status_code": status,
|
|
1697
|
+
"error_message": "The Org usage endpoint returned an unexpected payload.",
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
message = None
|
|
1701
|
+
if isinstance(data, dict):
|
|
1702
|
+
message = (
|
|
1703
|
+
data.get("message")
|
|
1704
|
+
or data.get("error")
|
|
1705
|
+
or data.get("detail")
|
|
1706
|
+
)
|
|
1707
|
+
return {
|
|
1708
|
+
"success": False,
|
|
1709
|
+
"status_code": status,
|
|
1710
|
+
"error_message": message or f"The Org responded with {status}",
|
|
1711
|
+
}
|
|
1712
|
+
except Exception as exc:
|
|
1713
|
+
logger.error(f"The Org connectivity test failed: {exc}")
|
|
1714
|
+
return {"success": False, "status_code": 0, "error_message": str(exc)}
|
|
1715
|
+
|
|
1716
|
+
|
|
1649
1717
|
###############################################################################
|
|
1650
1718
|
# DATAGMA CONNECTIVITY
|
|
1651
1719
|
###############################################################################
|
|
@@ -1739,12 +1807,14 @@ async def test_connectivity(tool_config: List[Dict[str, Any]]) -> Dict[str, Dict
|
|
|
1739
1807
|
"nooks": test_nooks,
|
|
1740
1808
|
"commonRoom": test_commonroom,
|
|
1741
1809
|
"scarf": test_scarf,
|
|
1810
|
+
"theorg": test_theorg,
|
|
1742
1811
|
"salesforce": test_salesforce,
|
|
1743
1812
|
"clay": test_clay,
|
|
1744
1813
|
"posthog": test_posthog,
|
|
1745
1814
|
"mcpServer": test_mcp_server,
|
|
1746
1815
|
"slack": test_slack,
|
|
1747
1816
|
"mailgun": test_mailgun,
|
|
1817
|
+
"mailreach": test_mailreach,
|
|
1748
1818
|
"sendgrid": test_sendgrid,
|
|
1749
1819
|
"samgov": test_samgov,
|
|
1750
1820
|
"scraperapi": test_scraperapi,
|
|
@@ -63,6 +63,7 @@ src/dhisana/utils/instantly_tools.py
|
|
|
63
63
|
src/dhisana/utils/linkedin_crawler.py
|
|
64
64
|
src/dhisana/utils/lusha_tools.py
|
|
65
65
|
src/dhisana/utils/mailgun_tools.py
|
|
66
|
+
src/dhisana/utils/mailreach_tools.py
|
|
66
67
|
src/dhisana/utils/microsoft365_tools.py
|
|
67
68
|
src/dhisana/utils/openai_assistant_and_file_utils.py
|
|
68
69
|
src/dhisana/utils/openai_helpers.py
|
|
@@ -110,6 +111,7 @@ tests/test_email_body_utils.py
|
|
|
110
111
|
tests/test_google_document.py
|
|
111
112
|
tests/test_hubspot_call_logs.py
|
|
112
113
|
tests/test_linkedin_serper.py
|
|
114
|
+
tests/test_mailreach.py
|
|
113
115
|
tests/test_mcp_connectivity.py
|
|
114
116
|
tests/test_proxycurl_get_company_search_id.py
|
|
115
117
|
tests/test_proxycurl_job_count.py
|
|
@@ -54,6 +54,22 @@ class TestConnectivity(unittest.TestCase):
|
|
|
54
54
|
self.assertTrue(result['firefliesai']['success'])
|
|
55
55
|
asyncio.run(runner())
|
|
56
56
|
|
|
57
|
+
def test_theorg_dispatch(self):
|
|
58
|
+
async def runner():
|
|
59
|
+
with patch('src.dhisana.utils.test_connect.test_theorg', new=AsyncMock(return_value={'success': True, 'status_code': 200, 'error_message': None})):
|
|
60
|
+
tool_config = [
|
|
61
|
+
{
|
|
62
|
+
'name': 'theorg',
|
|
63
|
+
'configuration': [
|
|
64
|
+
{'name': 'apiKey', 'value': 'dummy'}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
result = await run_test_connectivity(tool_config)
|
|
69
|
+
self.assertIn('theorg', result)
|
|
70
|
+
self.assertTrue(result['theorg']['success'])
|
|
71
|
+
asyncio.run(runner())
|
|
72
|
+
|
|
57
73
|
def test_salesforce_dispatch(self):
|
|
58
74
|
async def runner():
|
|
59
75
|
with patch(
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import unittest
|
|
3
|
+
import os
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
5
|
+
|
|
6
|
+
# Test imports
|
|
7
|
+
from src.dhisana.utils.mailreach_tools import (
|
|
8
|
+
get_mailreach_api_key,
|
|
9
|
+
get_mailreach_headers,
|
|
10
|
+
ping_mailreach
|
|
11
|
+
)
|
|
12
|
+
from src.dhisana.utils.test_connect import test_mailreach
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestMailReachTools(unittest.TestCase):
|
|
16
|
+
"""Test suite for MailReach integration tools."""
|
|
17
|
+
|
|
18
|
+
def test_get_mailreach_api_key_from_config(self):
|
|
19
|
+
"""Test retrieving API key from tool configuration."""
|
|
20
|
+
tool_config = [
|
|
21
|
+
{
|
|
22
|
+
'name': 'mailreach',
|
|
23
|
+
'configuration': [
|
|
24
|
+
{'name': 'apiKey', 'value': 'test_api_key_123'}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
api_key = get_mailreach_api_key(tool_config)
|
|
29
|
+
self.assertEqual(api_key, 'test_api_key_123')
|
|
30
|
+
|
|
31
|
+
def test_get_mailreach_api_key_from_env(self):
|
|
32
|
+
"""Test retrieving API key from environment variable."""
|
|
33
|
+
with patch.dict(os.environ, {'MAILREACH_API_KEY': 'env_api_key_456'}):
|
|
34
|
+
api_key = get_mailreach_api_key()
|
|
35
|
+
self.assertEqual(api_key, 'env_api_key_456')
|
|
36
|
+
|
|
37
|
+
def test_get_mailreach_api_key_missing(self):
|
|
38
|
+
"""Test that missing API key raises ValueError."""
|
|
39
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
40
|
+
with self.assertRaises(ValueError) as context:
|
|
41
|
+
get_mailreach_api_key()
|
|
42
|
+
self.assertIn('not configured', str(context.exception))
|
|
43
|
+
|
|
44
|
+
def test_get_mailreach_headers(self):
|
|
45
|
+
"""Test that headers are properly formatted."""
|
|
46
|
+
tool_config = [
|
|
47
|
+
{
|
|
48
|
+
'name': 'mailreach',
|
|
49
|
+
'configuration': [
|
|
50
|
+
{'name': 'apiKey', 'value': 'test_key'}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
headers = get_mailreach_headers(tool_config)
|
|
55
|
+
self.assertEqual(headers['x-api-key'], 'test_key')
|
|
56
|
+
self.assertEqual(headers['Content-Type'], 'application/json')
|
|
57
|
+
|
|
58
|
+
def test_ping_mailreach_success(self):
|
|
59
|
+
"""Test successful ping to MailReach API."""
|
|
60
|
+
async def runner():
|
|
61
|
+
tool_config = [
|
|
62
|
+
{
|
|
63
|
+
'name': 'mailreach',
|
|
64
|
+
'configuration': [
|
|
65
|
+
{'name': 'apiKey', 'value': 'test_api_key'}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
mock_response = AsyncMock()
|
|
71
|
+
mock_response.status = 200
|
|
72
|
+
mock_response.json = AsyncMock(return_value={'message': 'pong', 'status': 'ok'})
|
|
73
|
+
|
|
74
|
+
mock_get = AsyncMock()
|
|
75
|
+
mock_get.__aenter__.return_value = mock_response
|
|
76
|
+
|
|
77
|
+
with patch('aiohttp.ClientSession') as mock_session:
|
|
78
|
+
mock_session.return_value.__aenter__.return_value.get = MagicMock(return_value=mock_get)
|
|
79
|
+
|
|
80
|
+
result = await ping_mailreach(tool_config)
|
|
81
|
+
|
|
82
|
+
self.assertIsInstance(result, dict)
|
|
83
|
+
self.assertEqual(result.get('message'), 'pong')
|
|
84
|
+
|
|
85
|
+
asyncio.run(runner())
|
|
86
|
+
|
|
87
|
+
def test_ping_mailreach_rate_limit(self):
|
|
88
|
+
"""Test rate limit handling in ping."""
|
|
89
|
+
async def runner():
|
|
90
|
+
tool_config = [
|
|
91
|
+
{
|
|
92
|
+
'name': 'mailreach',
|
|
93
|
+
'configuration': [
|
|
94
|
+
{'name': 'apiKey', 'value': 'test_api_key'}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
mock_response = AsyncMock()
|
|
100
|
+
mock_response.status = 429
|
|
101
|
+
mock_response.text = AsyncMock(return_value='Rate limit exceeded')
|
|
102
|
+
mock_response.request_info = MagicMock()
|
|
103
|
+
mock_response.history = []
|
|
104
|
+
mock_response.headers = {}
|
|
105
|
+
|
|
106
|
+
mock_get = AsyncMock()
|
|
107
|
+
mock_get.__aenter__.return_value = mock_response
|
|
108
|
+
|
|
109
|
+
with patch('aiohttp.ClientSession') as mock_session:
|
|
110
|
+
mock_session.return_value.__aenter__.return_value.get = MagicMock(return_value=mock_get)
|
|
111
|
+
|
|
112
|
+
with self.assertRaises(Exception):
|
|
113
|
+
await ping_mailreach(tool_config)
|
|
114
|
+
|
|
115
|
+
asyncio.run(runner())
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestMailReachConnectivity(unittest.TestCase):
|
|
119
|
+
"""Test suite for MailReach connectivity check."""
|
|
120
|
+
|
|
121
|
+
def test_mailreach_connection_success(self):
|
|
122
|
+
"""Test successful MailReach connection."""
|
|
123
|
+
async def runner():
|
|
124
|
+
mock_response = AsyncMock()
|
|
125
|
+
mock_response.status = 200
|
|
126
|
+
mock_response.json = AsyncMock(return_value={'message': 'pong'})
|
|
127
|
+
|
|
128
|
+
mock_get = AsyncMock()
|
|
129
|
+
mock_get.__aenter__.return_value = mock_response
|
|
130
|
+
|
|
131
|
+
with patch('aiohttp.ClientSession') as mock_session:
|
|
132
|
+
mock_session.return_value.__aenter__.return_value.get = MagicMock(return_value=mock_get)
|
|
133
|
+
|
|
134
|
+
result = await test_mailreach('test_api_key')
|
|
135
|
+
|
|
136
|
+
self.assertTrue(result['success'])
|
|
137
|
+
self.assertEqual(result['status_code'], 200)
|
|
138
|
+
self.assertIsNone(result['error_message'])
|
|
139
|
+
|
|
140
|
+
asyncio.run(runner())
|
|
141
|
+
|
|
142
|
+
def test_mailreach_connection_invalid_key(self):
|
|
143
|
+
"""Test MailReach connection with invalid API key."""
|
|
144
|
+
async def runner():
|
|
145
|
+
mock_response = AsyncMock()
|
|
146
|
+
mock_response.status = 401
|
|
147
|
+
mock_response.json = AsyncMock(return_value={'error': 'Invalid API key'})
|
|
148
|
+
|
|
149
|
+
mock_get = AsyncMock()
|
|
150
|
+
mock_get.__aenter__.return_value = mock_response
|
|
151
|
+
|
|
152
|
+
with patch('aiohttp.ClientSession') as mock_session:
|
|
153
|
+
mock_session.return_value.__aenter__.return_value.get = MagicMock(return_value=mock_get)
|
|
154
|
+
|
|
155
|
+
result = await test_mailreach('invalid_key')
|
|
156
|
+
|
|
157
|
+
self.assertFalse(result['success'])
|
|
158
|
+
self.assertEqual(result['status_code'], 401)
|
|
159
|
+
self.assertIsNotNone(result['error_message'])
|
|
160
|
+
|
|
161
|
+
asyncio.run(runner())
|
|
162
|
+
|
|
163
|
+
def test_mailreach_connection_timeout(self):
|
|
164
|
+
"""Test MailReach connection timeout."""
|
|
165
|
+
async def runner():
|
|
166
|
+
with patch('aiohttp.ClientSession') as mock_session:
|
|
167
|
+
mock_session.return_value.__aenter__.return_value.get.side_effect = asyncio.TimeoutError()
|
|
168
|
+
|
|
169
|
+
result = await test_mailreach('test_api_key')
|
|
170
|
+
|
|
171
|
+
self.assertFalse(result['success'])
|
|
172
|
+
self.assertEqual(result['status_code'], 0)
|
|
173
|
+
self.assertIsNotNone(result['error_message'])
|
|
174
|
+
|
|
175
|
+
asyncio.run(runner())
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == '__main__':
|
|
179
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/check_email_validity_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/check_linkedin_url_validity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/compose_three_step_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/extract_email_content_for_llm.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/generate_linkedin_connect_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openai_assistant_and_file_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/openapi_tool/openapi_tool.py
RENAMED
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/parse_linkedin_messages_txt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serarch_router_local_business.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev238 → dhisana-0.0.1.dev240}/src/dhisana/utils/serpapi_local_business_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|