dhisana 0.0.1.dev301__tar.gz → 0.0.1.dev302__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 (124) hide show
  1. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/setup.py +1 -1
  3. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/apollo_tools.py +81 -2
  4. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_linkedin_url_validity.py +4 -1
  5. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/enrich_lead_information.py +8 -0
  6. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_search_tools.py +2 -0
  7. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/PKG-INFO +1 -1
  8. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/README.md +0 -0
  9. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/pyproject.toml +0 -0
  10. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/setup.cfg +0 -0
  11. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/__init__.py +0 -0
  12. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/__init__.py +0 -0
  13. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/cli.py +0 -0
  14. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/datasets.py +0 -0
  15. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/models.py +0 -0
  16. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/cli/predictions.py +0 -0
  17. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/schemas/__init__.py +0 -0
  18. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/schemas/common.py +0 -0
  19. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/schemas/sales.py +0 -0
  20. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/ui/__init__.py +0 -0
  21. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/ui/components.py +0 -0
  22. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/__init__.py +0 -0
  23. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/add_mapping.py +0 -0
  24. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/agent_tools.py +0 -0
  25. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  26. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/built_with_api_tools.py +0 -0
  27. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/cache_output_tools.py +0 -0
  28. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  29. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  30. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  31. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/clay_tools.py +0 -0
  32. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/clean_properties.py +0 -0
  33. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/company_utils.py +0 -0
  34. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  35. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_search_query.py +0 -0
  36. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  37. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/composite_tools.py +0 -0
  38. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/dataframe_tools.py +0 -0
  39. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/domain_parser.py +0 -0
  40. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/email_body_utils.py +0 -0
  41. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/email_parse_helpers.py +0 -0
  42. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/email_provider.py +0 -0
  43. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  44. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/fetch_openai_config.py +0 -0
  45. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/field_validators.py +0 -0
  46. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/g2_tools.py +0 -0
  47. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_content.py +0 -0
  48. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_custom_message.py +0 -0
  49. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_email.py +0 -0
  50. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_email_response.py +0 -0
  51. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_flow.py +0 -0
  52. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  53. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
  54. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  55. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  56. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/google_custom_search.py +0 -0
  57. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/google_oauth_tools.py +0 -0
  58. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/google_workspace_tools.py +0 -0
  59. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  60. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  61. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/instantly_tools.py +0 -0
  62. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/linkedin_crawler.py +0 -0
  63. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/lusha_tools.py +0 -0
  64. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/mailgun_tools.py +0 -0
  65. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/mailreach_tools.py +0 -0
  66. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/microsoft365_tools.py +0 -0
  67. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  68. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openai_helpers.py +0 -0
  69. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  70. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  71. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  72. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  73. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  74. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  75. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/profile.py +0 -0
  76. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/proxy_curl_tools.py +0 -0
  77. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  78. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/python_function_to_tools.py +0 -0
  79. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/research_lead.py +0 -0
  80. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  81. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  82. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/search_router.py +0 -0
  83. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/search_router_jobs.py +0 -0
  84. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/sendgrid_tools.py +0 -0
  85. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  86. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  87. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  88. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_google_search.py +0 -0
  89. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  90. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  91. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serperdev_local_business.py +0 -0
  92. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/serperdev_search.py +0 -0
  93. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/smtp_email_tools.py +0 -0
  94. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/test_connect.py +0 -0
  95. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/trasform_json.py +0 -0
  96. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  97. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/workflow_code_model.py +0 -0
  98. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/utils/zoominfo_tools.py +0 -0
  99. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/__init__.py +0 -0
  100. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/agent.py +0 -0
  101. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/flow.py +0 -0
  102. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/task.py +0 -0
  103. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana/workflow/test.py +0 -0
  104. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/SOURCES.txt +0 -0
  105. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/dependency_links.txt +0 -0
  106. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/entry_points.txt +0 -0
  107. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/requires.txt +0 -0
  108. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/src/dhisana.egg-info/top_level.txt +0 -0
  109. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_agent_tools.py +0 -0
  110. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_apollo_company_search.py +0 -0
  111. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_apollo_lead_search.py +0 -0
  112. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_connectivity.py +0 -0
  113. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_email_body_utils.py +0 -0
  114. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_generate_email.py +0 -0
  115. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_google_document.py +0 -0
  116. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_hubspot_call_logs.py +0 -0
  117. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_linkedin_serper.py +0 -0
  118. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_mailreach.py +0 -0
  119. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_mcp_connectivity.py +0 -0
  120. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_normalize_graph_datetime.py +0 -0
  121. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_proxycurl_get_company_search_id.py +0 -0
  122. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_proxycurl_job_count.py +0 -0
  123. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/tests/test_reply_thread_fallback.py +0 -0
  124. {dhisana-0.0.1.dev301 → dhisana-0.0.1.dev302}/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.dev301
3
+ Version: 0.0.1.dev302
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-dev301',
5
+ version='0.0.1-dev302',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -11,12 +11,15 @@ from dhisana.utils.assistant_tool_tag import assistant_tool
11
11
  from urllib.parse import urlparse, parse_qs
12
12
  from typing import Any, Dict, List, Optional, Tuple, Union
13
13
 
14
+ from dhisana.utils.cache_output_tools import cache_output, retrieve_output
14
15
  from dhisana.utils.clean_properties import cleanup_properties
15
16
  from dhisana.utils.company_utils import normalize_company_name
16
17
 
17
18
  logging.basicConfig(level=logging.INFO)
18
19
  logger = logging.getLogger(__name__)
19
20
 
21
+ APOLLO_CACHE_TTL = 14 * 24 * 60 * 60 # 14 days in seconds
22
+
20
23
 
21
24
  def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> Tuple[str, bool]:
22
25
  """
@@ -109,6 +112,7 @@ async def enrich_person_info_from_apollo(
109
112
  phone: Optional[str] = None,
110
113
  fetch_valid_phone_number: Optional[bool] = False,
111
114
  tool_config: Optional[List[Dict]] = None,
115
+ organization_id: Optional[str] = None,
112
116
  ) -> Dict[str, Any]:
113
117
  """
114
118
  Fetch a person's details from Apollo using LinkedIn URL, email, or phone number.
@@ -118,6 +122,7 @@ async def enrich_person_info_from_apollo(
118
122
  - **email** (*str*, optional): Email address of the person.
119
123
  - **phone** (*str*, optional): Phone number of the person.
120
124
  - **fetch_valid_phone_number** (*bool*, optional): If True, include phone numbers in the API response. Defaults to False.
125
+ - **organization_id** (*str*, optional): Organization ID used as prefix for cache keys to scope caching per tenant.
121
126
 
122
127
  Returns:
123
128
  - **dict**: JSON response containing person information.
@@ -130,6 +135,21 @@ async def enrich_person_info_from_apollo(
130
135
  logger.warning("No linkedin_url, email, or phone provided. At least one is required.")
131
136
  return {'error': "At least one of linkedin_url, email, or phone must be provided"}
132
137
 
138
+ # Check cache if organization_id is available
139
+ cache_key_identifier = linkedin_url or email or phone
140
+ cache_tool_name = "enrich_person_phone_from_apollo" if fetch_valid_phone_number else "enrich_person_info_from_apollo"
141
+ if organization_id and cache_key_identifier:
142
+ cache_key = f"{organization_id}:{cache_key_identifier}"
143
+ try:
144
+ cached_response = retrieve_output(cache_tool_name, cache_key)
145
+ if cached_response is not None and cached_response.get('error') is None:
146
+ logger.info(f"Cache hit for Apollo person enrichment, org_id={organization_id}, identifier={cache_key_identifier}")
147
+ return cached_response
148
+ else:
149
+ logger.info(f"Cache miss for Apollo person enrichment, org_id={organization_id}, identifier={cache_key_identifier}")
150
+ except Exception:
151
+ logger.warning(f"Failed to retrieve cached Apollo person data for org_id={organization_id}, proceeding with API call.")
152
+
133
153
  headers = {"Content-Type": "application/json"}
134
154
  if is_oauth:
135
155
  headers["Authorization"] = f"Bearer {token}"
@@ -160,6 +180,13 @@ async def enrich_person_info_from_apollo(
160
180
  logger.debug(f"Received response status: {response.status}")
161
181
  if response.status == 200:
162
182
  result = await response.json()
183
+ # Cache on success if organization_id is available
184
+ if organization_id and cache_key_identifier:
185
+ cache_key = f"{organization_id}:{cache_key_identifier}"
186
+ try:
187
+ cache_output(cache_tool_name, cache_key, result, ttl=APOLLO_CACHE_TTL)
188
+ except Exception:
189
+ logger.warning("Failed to cache Apollo person data.")
163
190
  logger.info("Successfully retrieved person info from Apollo.")
164
191
  return result
165
192
  elif response.status == 429:
@@ -272,12 +299,14 @@ async def lookup_person_in_apollo_by_name(
272
299
  async def enrich_organization_info_from_apollo(
273
300
  organization_domain: Optional[str] = None,
274
301
  tool_config: Optional[List[Dict]] = None,
302
+ organization_id: Optional[str] = None,
275
303
  ) -> Dict[str, Any]:
276
304
  """
277
305
  Fetch an organization's details from Apollo using the organization domain.
278
306
 
279
307
  Parameters:
280
308
  - **organization_domain** (*str*, optional): Domain of the organization.
309
+ - **organization_id** (*str*, optional): Organization ID used as prefix for cache keys to scope caching per tenant.
281
310
 
282
311
  Returns:
283
312
  - **dict**: JSON response containing organization information.
@@ -290,6 +319,19 @@ async def enrich_organization_info_from_apollo(
290
319
  logger.warning("No organization domain provided.")
291
320
  return {'error': "organization domain must be provided"}
292
321
 
322
+ # Check cache if organization_id is available
323
+ if organization_id and organization_domain:
324
+ cache_key = f"{organization_id}:{organization_domain}"
325
+ try:
326
+ cached_response = retrieve_output("enrich_organization_info_from_apollo", cache_key)
327
+ if cached_response is not None and cached_response.get('error') is None:
328
+ logger.info(f"Cache hit for Apollo org enrichment, org_id={organization_id}, domain={organization_domain}")
329
+ return cached_response
330
+ else:
331
+ logger.info(f"Cache miss for Apollo org enrichment, org_id={organization_id}, domain={organization_domain}")
332
+ except Exception:
333
+ logger.warning(f"Failed to retrieve cached Apollo org data for org_id={organization_id}, proceeding with API call.")
334
+
293
335
  headers = {
294
336
  "Content-Type": "application/json",
295
337
  "Cache-Control": "no-cache",
@@ -309,6 +351,13 @@ async def enrich_organization_info_from_apollo(
309
351
  logger.debug(f"Received response status: {response.status}")
310
352
  if response.status == 200:
311
353
  result = await response.json()
354
+ # Cache on success if organization_id is available
355
+ if organization_id and organization_domain:
356
+ cache_key = f"{organization_id}:{organization_domain}"
357
+ try:
358
+ cache_output("enrich_organization_info_from_apollo", cache_key, result, ttl=APOLLO_CACHE_TTL)
359
+ except Exception:
360
+ logger.warning("Failed to cache Apollo org data.")
312
361
  logger.info("Successfully retrieved organization info from Apollo.")
313
362
  return result
314
363
  elif response.status == 429:
@@ -1117,6 +1166,10 @@ async def enrich_user_info_with_apollo(
1117
1166
  email = input_user_properties.get("email", "")
1118
1167
  user_data_from_apollo = None
1119
1168
 
1169
+ # Extract organization_id for tenant-scoped caching
1170
+ org_id_raw = input_user_properties.get("organization_id", "")
1171
+ organization_id = str(org_id_raw) if org_id_raw else None
1172
+
1120
1173
  logger.debug(f"Properties => LinkedIn URL: {linkedin_url}, Email: {email}")
1121
1174
 
1122
1175
  # If LinkedIn url or email is present, attempt direct enrichment
@@ -1125,7 +1178,8 @@ async def enrich_user_info_with_apollo(
1125
1178
  user_data_from_apollo = await enrich_person_info_from_apollo(
1126
1179
  linkedin_url=linkedin_url,
1127
1180
  email=email,
1128
- tool_config=tool_config
1181
+ tool_config=tool_config,
1182
+ organization_id=organization_id,
1129
1183
  )
1130
1184
  except Exception:
1131
1185
  logger.exception("Exception occurred while enriching person info from Apollo by LinkedIn or email.")
@@ -1185,7 +1239,8 @@ async def enrich_user_info_with_apollo(
1185
1239
  try:
1186
1240
  user_data_from_apollo = await enrich_person_info_from_apollo(
1187
1241
  linkedin_url=linkedin_url,
1188
- tool_config=tool_config
1242
+ tool_config=tool_config,
1243
+ organization_id=organization_id,
1189
1244
  )
1190
1245
  except Exception:
1191
1246
  logger.exception("Exception occurred during second stage Apollo enrichment.")
@@ -1894,6 +1949,7 @@ async def search_organization_by_linkedin_or_domain(
1894
1949
  linkedin_url: Optional[str] = None,
1895
1950
  domain: Optional[str] = None,
1896
1951
  tool_config: Optional[List[Dict]] = None,
1952
+ organization_id: Optional[str] = None,
1897
1953
  ) -> Dict[str, Any]:
1898
1954
  """
1899
1955
  Search for an organization in Apollo using LinkedIn URL or domain and return
@@ -1933,6 +1989,21 @@ async def search_organization_by_linkedin_or_domain(
1933
1989
  logger.warning("No linkedin_url or domain provided. At least one is required.")
1934
1990
  return {'error': "At least one of linkedin_url or domain must be provided"}
1935
1991
 
1992
+ # --- Cache lookup ---
1993
+ cache_tool_name = "search_organization_by_linkedin_or_domain"
1994
+ cache_identifier = (linkedin_url or domain or "").strip().rstrip("/").lower()
1995
+ cache_key = f"{organization_id}:{cache_identifier}" if organization_id else None
1996
+ if cache_key:
1997
+ try:
1998
+ cached = retrieve_output(cache_tool_name, cache_key)
1999
+ if cached is not None and cached.get('error') is None:
2000
+ logger.info("Cache hit for search_organization_by_linkedin_or_domain org_id=%s identifier=%s", organization_id, cache_identifier)
2001
+ return cached
2002
+ else:
2003
+ logger.info("Cache miss for search_organization_by_linkedin_or_domain org_id=%s identifier=%s", organization_id, cache_identifier)
2004
+ except Exception as e:
2005
+ logger.warning("Cache retrieval error for search_organization_by_linkedin_or_domain: %s", e)
2006
+
1936
2007
  token, is_oauth = get_apollo_access_token(tool_config)
1937
2008
 
1938
2009
  headers = {
@@ -2126,6 +2197,14 @@ async def search_organization_by_linkedin_or_domain(
2126
2197
  f"(domain: {standardized_org.get('domain')}, "
2127
2198
  f"linkedin: {standardized_org.get('organization_linkedin_url')}, "
2128
2199
  f"confidence: {match_confidence})")
2200
+
2201
+ # --- Cache the result ---
2202
+ if cache_key:
2203
+ try:
2204
+ cache_output(cache_tool_name, cache_key, standardized_org, APOLLO_CACHE_TTL)
2205
+ except Exception as e:
2206
+ logger.warning("Cache write error for search_organization_by_linkedin_or_domain: %s", e)
2207
+
2129
2208
  return standardized_org
2130
2209
 
2131
2210
  elif response.status == 429:
@@ -68,9 +68,12 @@ async def validate_linkedin_url_with_apollo(
68
68
  linkedin_url = lead_properties.get("user_linkedin_url", "")
69
69
  match_result = LeadLinkedInMatch()
70
70
 
71
+ org_id_raw = lead_properties.get("organization_id", "")
72
+ apollo_org_id = str(org_id_raw) if org_id_raw else None
71
73
  linkedin_data = await enrich_person_info_from_apollo(
72
74
  linkedin_url=linkedin_url,
73
- tool_config=tool_config
75
+ tool_config=tool_config,
76
+ organization_id=apollo_org_id,
74
77
  )
75
78
  # If no data is returned from Apollo, return defaults
76
79
  if not linkedin_data:
@@ -361,9 +361,12 @@ async def enrich_lead_information(
361
361
  and not cloned_properties.get("user_linkedin_url")
362
362
  ):
363
363
  try:
364
+ org_id_raw = cloned_properties.get("organization_id", "")
365
+ apollo_org_id = str(org_id_raw) if org_id_raw else None
364
366
  apollo_result = await enrich_person_info_from_apollo(
365
367
  email=cloned_properties["email"],
366
368
  tool_config=tool_config,
369
+ organization_id=apollo_org_id,
367
370
  )
368
371
  if apollo_result and not apollo_result.get("error"):
369
372
  person_data = apollo_result.get("person", {})
@@ -583,9 +586,12 @@ async def enrich_user_info(
583
586
  # 1a) If email is present, first try Apollo lookup by email for more robust matching
584
587
  if email:
585
588
  logger.debug("Attempting Apollo lookup by email: %s", email)
589
+ org_id_raw = input_properties.get("organization_id", "")
590
+ apollo_org_id = str(org_id_raw) if org_id_raw else None
586
591
  apollo_result = await enrich_person_info_from_apollo(
587
592
  email=email,
588
593
  tool_config=tool_config,
594
+ organization_id=apollo_org_id,
589
595
  )
590
596
  if apollo_result and not apollo_result.get("error"):
591
597
  person_data = apollo_result.get("person", {})
@@ -899,6 +905,7 @@ async def enrich_organization_info_from_company_url(
899
905
  extra: Optional[bool] = None,
900
906
  use_cache: Optional[str] = "if-present",
901
907
  fallback_to_cache: Optional[str] = "on-error",
908
+ organization_id: Optional[str] = None,
902
909
  ) -> Dict[str, Any]:
903
910
  """
904
911
  Given an organization LinkedIn URL, attempt to enrich its data (e.g. name, website)
@@ -917,6 +924,7 @@ async def enrich_organization_info_from_company_url(
917
924
  apollo_result = await search_organization_by_linkedin_or_domain(
918
925
  linkedin_url=organization_linkedin_url,
919
926
  tool_config=tool_config,
927
+ organization_id=organization_id,
920
928
  )
921
929
  if apollo_result and not apollo_result.get("error"):
922
930
  logger.debug(f"Apollo returned company data: {apollo_result.get('organization_name')}")
@@ -828,6 +828,7 @@ async def get_resolved_linkedin_links(url: str) -> List[str]:
828
828
  async def get_company_website_from_linkedin_url(
829
829
  linkedin_url: str,
830
830
  tool_config: Optional[List[Dict]] = None,
831
+ organization_id: Optional[str] = None,
831
832
  ) -> str:
832
833
  """
833
834
  Attempt to extract a company's website from its LinkedIn URL.
@@ -859,6 +860,7 @@ async def get_company_website_from_linkedin_url(
859
860
  org_info = await search_organization_by_linkedin_or_domain(
860
861
  linkedin_url=linkedin_url,
861
862
  tool_config=tool_config,
863
+ organization_id=organization_id,
862
864
  )
863
865
  # Prefer 'website', then build one from 'domain'
864
866
  website = (org_info.get("website") or "").strip()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev301
3
+ Version: 0.0.1.dev302
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
File without changes
File without changes