dhisana 0.0.1.dev218__tar.gz → 0.0.1.dev219__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.
Files changed (115) hide show
  1. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/setup.py +1 -1
  3. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/schemas/sales.py +67 -27
  4. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/test_connect.py +109 -1
  5. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana.egg-info/PKG-INFO +1 -1
  6. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_connectivity.py +23 -4
  7. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/README.md +0 -0
  8. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/pyproject.toml +0 -0
  9. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/setup.cfg +0 -0
  10. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/__init__.py +0 -0
  11. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/cli/__init__.py +0 -0
  12. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/cli/cli.py +0 -0
  13. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/cli/datasets.py +0 -0
  14. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/cli/models.py +0 -0
  15. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/cli/predictions.py +0 -0
  16. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/schemas/__init__.py +0 -0
  17. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/schemas/common.py +0 -0
  18. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/ui/__init__.py +0 -0
  19. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/ui/components.py +0 -0
  20. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/__init__.py +0 -0
  21. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/add_mapping.py +0 -0
  22. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/agent_tools.py +0 -0
  23. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/apollo_tools.py +0 -0
  24. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  25. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/built_with_api_tools.py +0 -0
  26. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/cache_output_tools.py +0 -0
  27. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  28. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  29. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  30. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  31. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/clay_tools.py +0 -0
  32. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/clean_properties.py +0 -0
  33. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/company_utils.py +0 -0
  34. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  35. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/compose_search_query.py +0 -0
  36. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  37. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/composite_tools.py +0 -0
  38. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/dataframe_tools.py +0 -0
  39. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/domain_parser.py +0 -0
  40. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/email_parse_helpers.py +0 -0
  41. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/email_provider.py +0 -0
  42. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/enrich_lead_information.py +0 -0
  43. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  44. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/fetch_openai_config.py +0 -0
  45. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/field_validators.py +0 -0
  46. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/g2_tools.py +0 -0
  47. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_content.py +0 -0
  48. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_email.py +0 -0
  49. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_email_response.py +0 -0
  50. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_flow.py +0 -0
  51. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  52. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
  53. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  54. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  55. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/google_custom_search.py +0 -0
  56. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/google_oauth_tools.py +0 -0
  57. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/google_workspace_tools.py +0 -0
  58. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  59. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  60. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/instantly_tools.py +0 -0
  61. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/linkedin_crawler.py +0 -0
  62. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/lusha_tools.py +0 -0
  63. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/mailgun_tools.py +0 -0
  64. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/microsoft365_tools.py +0 -0
  65. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  66. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openai_helpers.py +0 -0
  67. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  68. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  69. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  70. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  71. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  72. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  73. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/profile.py +0 -0
  74. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/proxy_curl_tools.py +0 -0
  75. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  76. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/python_function_to_tools.py +0 -0
  77. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/research_lead.py +0 -0
  78. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  79. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  80. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/search_router.py +0 -0
  81. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/search_router_jobs.py +0 -0
  82. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/sendgrid_tools.py +0 -0
  83. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  84. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  85. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  86. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serpapi_google_search.py +0 -0
  87. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  88. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  89. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  90. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serperdev_local_business.py +0 -0
  91. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/serperdev_search.py +0 -0
  92. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/smtp_email_tools.py +0 -0
  93. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/trasform_json.py +0 -0
  94. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  95. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/workflow_code_model.py +0 -0
  96. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/utils/zoominfo_tools.py +0 -0
  97. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/workflow/__init__.py +0 -0
  98. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/workflow/agent.py +0 -0
  99. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/workflow/flow.py +0 -0
  100. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/workflow/task.py +0 -0
  101. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana/workflow/test.py +0 -0
  102. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana.egg-info/SOURCES.txt +0 -0
  103. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana.egg-info/dependency_links.txt +0 -0
  104. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana.egg-info/entry_points.txt +0 -0
  105. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana.egg-info/requires.txt +0 -0
  106. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/src/dhisana.egg-info/top_level.txt +0 -0
  107. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_agent_tools.py +0 -0
  108. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_apollo_company_search.py +0 -0
  109. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_google_document.py +0 -0
  110. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_hubspot_call_logs.py +0 -0
  111. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_linkedin_serper.py +0 -0
  112. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_mcp_connectivity.py +0 -0
  113. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_proxycurl_get_company_search_id.py +0 -0
  114. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_proxycurl_job_count.py +0 -0
  115. {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev219}/tests/test_structured_output_with_mcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev218
3
+ Version: 0.0.1.dev219
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
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev218',
5
+ version='0.0.1-dev219',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -1,3 +1,5 @@
1
+ import json
2
+
1
3
  from uuid import UUID
2
4
  from pydantic import BaseModel, Field, field_validator
3
5
  from typing import List, Optional, Dict, Any
@@ -10,14 +12,20 @@ from typing import Optional, List, Dict, Literal
10
12
  # -----------------------------
11
13
 
12
14
  class Lead(BaseModel):
13
- id: Optional[str] = None
15
+ id: Optional[UUID] = None
14
16
  full_name: Optional[str] = None
15
17
  first_name: Optional[str] = None
16
18
  last_name: Optional[str] = None
17
19
  email: Optional[str] = None
18
20
  user_linkedin_url: Optional[str] = None
19
21
  user_linkedin_salesnav_url: Optional[str] = None
22
+ organization_linkedin_url: Optional[str] = None
23
+ organization_linkedin_salesnav_url: Optional[str] = None
24
+ linkedin_follower_count: Optional[int] = None
20
25
  primary_domain_of_organization: Optional[str] = None
26
+ twitter_handle: Optional[str] = None
27
+ twitch_handle: Optional[str] = None
28
+ github_handle: Optional[str] = None
21
29
  job_title: Optional[str] = None
22
30
  phone: Optional[str] = None
23
31
  headline: Optional[str] = None
@@ -25,36 +33,68 @@ class Lead(BaseModel):
25
33
  organization_name: Optional[str] = None
26
34
  organization_website: Optional[str] = None
27
35
  summary_about_lead: Optional[str] = None
36
+
37
+ qualification_score: Optional[float] = None
38
+ qualification_reason: Optional[str] = None
39
+ revenue: Optional[str] = None
40
+ company_size: Optional[str] = None
41
+ industry: Optional[str] = None
42
+
43
+ keywords: Optional[Any] = None
44
+ tags: List[str] = []
45
+ notes: List[str] = []
46
+ additional_properties: Optional[Dict[str, Any]] = {}
28
47
  workflow_stage: Optional[str] = None
29
- assigned_to: Optional[str] = None
30
- engaged: Optional[bool] = None
48
+
49
+ engaged: bool = False
31
50
  last_contact: Optional[int] = None
32
- additional_properties: Optional[Dict[str, Any]] = None
33
51
  research_summary: Optional[str] = None
34
- task_ids: Optional[List[str]] = None
35
- email_validation_status: Optional[
36
- Literal["not_started", "in_progress", "valid", "invalid"]
37
- ] = None
38
- linkedin_validation_status: Optional[
39
- Literal["not_started", "in_progress", "valid", "invalid"]
40
- ] = None
41
- research_status: Optional[
42
- Literal["not_started", "in_progress", "done", "failed"]
43
- ] = None
44
- enchrichment_status: Optional[
45
- Literal["not_started", "in_progress", "done", "failed"]
46
- ] = None
47
-
48
- @field_validator(
49
- "linkedin_validation_status",
50
- "email_validation_status",
51
- "research_status",
52
- "enchrichment_status",
53
- mode="before"
54
- )
52
+ email_validation_status: Optional[str] = None
53
+ linkedin_validation_status: Optional[str] = None
54
+ research_status: Optional[str] = None
55
+ enchrichment_status: Optional[str] = None
56
+
57
+
58
+ @field_validator("linkedin_follower_count", mode="before")
59
+ @classmethod
60
+ def parse_linkedin_follower_count(cls, v):
61
+ if v is None or v == "":
62
+ return None
63
+ if isinstance(v, str):
64
+ v = v.strip()
65
+ if v == "":
66
+ return None
67
+ try:
68
+ return int(v)
69
+ except ValueError:
70
+ raise ValueError("linkedin_follower_count must be an integer")
71
+ return v
72
+
73
+ @field_validator("notes", mode="before")
55
74
  @classmethod
56
- def empty_string_to_none(cls, v):
57
- return None if v == "" else v
75
+ def ensure_notes_list(cls, v):
76
+ """Coerce notes to a list of strings.
77
+ Handles legacy cases where the DB may contain a scalar or JSON string.
78
+ """
79
+ if v is None:
80
+ return []
81
+ if isinstance(v, list):
82
+ # Ensure all elements are strings
83
+ return [str(item) if not isinstance(item, str) else item for item in v]
84
+ if isinstance(v, str):
85
+ # Try to parse JSON array; if not, wrap as single-note list
86
+ try:
87
+ parsed = json.loads(v)
88
+ if isinstance(parsed, list):
89
+ return [str(item) if not isinstance(item, str) else item for item in parsed]
90
+ except Exception:
91
+ pass
92
+ return [v]
93
+ # Fallback: wrap any other scalar/object as a single string entry
94
+ try:
95
+ return [json.dumps(v)]
96
+ except Exception:
97
+ return [str(v)]
58
98
 
59
99
 
60
100
  class LeadList(BaseModel):
@@ -2,6 +2,7 @@ import json
2
2
  import logging
3
3
  import asyncio
4
4
  import os
5
+ from datetime import datetime, timedelta, timezone
5
6
  from typing import Awaitable, Callable, Dict, List, Any, Optional
6
7
  import requests
7
8
 
@@ -420,6 +421,112 @@ async def test_sendgrid(api_key: str) -> Dict[str, Any]:
420
421
  return {"success": False, "status_code": 0, "error_message": str(e)}
421
422
 
422
423
 
424
+ async def test_samgov(api_key: str) -> Dict[str, Any]:
425
+ """Test SAM.gov connectivity by fetching a single opportunity."""
426
+
427
+ url = "https://api.sam.gov/opportunities/v2/search"
428
+ now = datetime.now(timezone.utc)
429
+ posted_to = now.strftime("%Y-%m-%dT%H:%M:%SZ")
430
+ posted_from = (now - timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")
431
+
432
+ params = {
433
+ "limit": 1,
434
+ "offset": 0,
435
+ "keyword": "software",
436
+ "status": "active",
437
+ "includeCount": "true",
438
+ "postedFrom": posted_from,
439
+ "postedTo": posted_to,
440
+ "api_key": api_key,
441
+ }
442
+
443
+ try:
444
+ async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
445
+
446
+ async def perform(request_params: Dict[str, Any]):
447
+ async with session.get(url, params=request_params) as response:
448
+ status = response.status
449
+ body_text = await response.text()
450
+ data: Optional[Dict[str, Any]] = None
451
+
452
+ try:
453
+ parsed = json.loads(body_text)
454
+ if isinstance(parsed, dict):
455
+ data = parsed
456
+ except json.JSONDecodeError:
457
+ data = None
458
+
459
+ return status, data, body_text
460
+
461
+ status, data, body_text = await perform(params)
462
+
463
+ def extract_error_message(payload: Optional[Dict[str, Any]], fallback_text: str) -> Optional[str]:
464
+ if not payload:
465
+ return fallback_text[:200] if fallback_text else None
466
+
467
+ errors = payload.get("errors") or payload.get("error")
468
+ if isinstance(errors, list):
469
+ parts = [
470
+ err.get("message") if isinstance(err, dict) else str(err)
471
+ for err in errors
472
+ if err
473
+ ]
474
+ return "; ".join(parts) if parts else fallback_text[:200]
475
+ if isinstance(errors, dict):
476
+ return errors.get("message") or str(errors)
477
+ if errors:
478
+ return str(errors)
479
+
480
+ for key in ("message", "errorMessage", "detail", "description"):
481
+ if key in payload and payload[key]:
482
+ return str(payload[key])
483
+
484
+ return fallback_text[:200] if fallback_text else None
485
+
486
+ error_message = extract_error_message(data, body_text)
487
+
488
+ if status == 400 and error_message and "Invalid Date Entered" in error_message:
489
+ fallback_params = dict(params)
490
+ fallback_params["postedFrom"] = (now - timedelta(days=7)).strftime("%m/%d/%Y")
491
+ fallback_params["postedTo"] = now.strftime("%m/%d/%Y")
492
+ status, data, body_text = await perform(fallback_params)
493
+ error_message = extract_error_message(data, body_text)
494
+
495
+ if status != 200:
496
+ return {
497
+ "success": False,
498
+ "status_code": status,
499
+ "error_message": error_message or f"SAM.gov non-200: {status}",
500
+ }
501
+
502
+ if not data:
503
+ return {
504
+ "success": False,
505
+ "status_code": status,
506
+ "error_message": "SAM.gov returned invalid JSON response.",
507
+ }
508
+
509
+ if data.get("errors"):
510
+ return {
511
+ "success": False,
512
+ "status_code": status,
513
+ "error_message": extract_error_message(data, body_text) or "SAM.gov reported errors.",
514
+ }
515
+
516
+ if data.get("opportunitiesData") or data.get("totalRecords") is not None:
517
+ return {"success": True, "status_code": status, "error_message": None}
518
+
519
+ return {
520
+ "success": False,
521
+ "status_code": status,
522
+ "error_message": "Unexpected SAM.gov response payload.",
523
+ }
524
+
525
+ except Exception as e:
526
+ logger.error(f"SAM.gov test failed: {e}")
527
+ return {"success": False, "status_code": 0, "error_message": str(e)}
528
+
529
+
423
530
  async def test_salesforce(
424
531
  username: str,
425
532
  password: str,
@@ -1086,8 +1193,9 @@ async def test_connectivity(tool_config: List[Dict[str, Any]]) -> Dict[str, Dict
1086
1193
  "clay": test_clay,
1087
1194
  "mcpServer": test_mcp_server,
1088
1195
  "slack": test_slack,
1089
- "mailgun": test_mailgun,
1196
+ "mailgun": test_mailgun,
1090
1197
  "sendgrid": test_sendgrid,
1198
+ "samgov": test_samgov,
1091
1199
  }
1092
1200
 
1093
1201
  results: Dict[str, Dict[str, Any]] = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev218
3
+ Version: 0.0.1.dev219
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
@@ -2,7 +2,7 @@ import asyncio
2
2
  import unittest
3
3
  from unittest.mock import AsyncMock, patch
4
4
 
5
- from src.dhisana.utils.test_connect import test_connectivity
5
+ from src.dhisana.utils.test_connect import test_connectivity as run_test_connectivity
6
6
 
7
7
 
8
8
  class TestConnectivity(unittest.TestCase):
@@ -17,7 +17,7 @@ class TestConnectivity(unittest.TestCase):
17
17
  ]
18
18
  }
19
19
  ]
20
- result = await test_connectivity(tool_config)
20
+ result = await run_test_connectivity(tool_config)
21
21
  self.assertIn('jinaai', result)
22
22
  self.assertTrue(result['jinaai']['success'])
23
23
  asyncio.run(runner())
@@ -33,7 +33,7 @@ class TestConnectivity(unittest.TestCase):
33
33
  ]
34
34
  }
35
35
  ]
36
- result = await test_connectivity(tool_config)
36
+ result = await run_test_connectivity(tool_config)
37
37
  self.assertIn('firecrawl', result)
38
38
  self.assertTrue(result['firecrawl']['success'])
39
39
  asyncio.run(runner())
@@ -57,11 +57,30 @@ class TestConnectivity(unittest.TestCase):
57
57
  ]
58
58
  }
59
59
  ]
60
- result = await test_connectivity(tool_config)
60
+ result = await run_test_connectivity(tool_config)
61
61
  self.assertIn('salesforce', result)
62
62
  self.assertTrue(result['salesforce']['success'])
63
63
  asyncio.run(runner())
64
64
 
65
+ def test_samgov_dispatch(self):
66
+ async def runner():
67
+ with patch(
68
+ 'src.dhisana.utils.test_connect.test_samgov',
69
+ new=AsyncMock(return_value={'success': True, 'status_code': 200, 'error_message': None})
70
+ ):
71
+ tool_config = [
72
+ {
73
+ 'name': 'samgov',
74
+ 'configuration': [
75
+ {'name': 'apiKey', 'value': 'dummy'}
76
+ ]
77
+ }
78
+ ]
79
+ result = await run_test_connectivity(tool_config)
80
+ self.assertIn('samgov', result)
81
+ self.assertTrue(result['samgov']['success'])
82
+ asyncio.run(runner())
83
+
65
84
 
66
85
  if __name__ == '__main__':
67
86
  unittest.main()
File without changes
File without changes