dhisana 0.0.1.dev7__tar.gz → 0.0.1.dev9__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 (42) hide show
  1. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/PKG-INFO +3 -1
  2. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/setup.py +4 -2
  3. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/ui/components.py +129 -45
  4. dhisana-0.0.1.dev9/src/dhisana/utils/agent_tools.py +48 -0
  5. dhisana-0.0.1.dev9/src/dhisana/utils/apollo_tools.py +121 -0
  6. dhisana-0.0.1.dev9/src/dhisana/utils/check_email_validity_tools.py +36 -0
  7. dhisana-0.0.1.dev9/src/dhisana/utils/dataframe_tools.py +202 -0
  8. dhisana-0.0.1.dev7/src/dhisana/utils/agent_tools.py → dhisana-0.0.1.dev9/src/dhisana/utils/google_workspace_tools.py +337 -271
  9. dhisana-0.0.1.dev9/src/dhisana/utils/hubspot_crm_tools.py +598 -0
  10. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/linkedin_crawler.py +8 -9
  11. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/openai_helpers.py +16 -8
  12. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/openapi_spec_to_tools.py +16 -6
  13. dhisana-0.0.1.dev9/src/dhisana/utils/python_function_to_tools.py +82 -0
  14. dhisana-0.0.1.dev9/src/dhisana/utils/salesforce_crm_tools.py +202 -0
  15. dhisana-0.0.1.dev9/src/dhisana/utils/tools_json.py +2 -0
  16. dhisana-0.0.1.dev9/src/dhisana/utils/web_download_parse_tools.py +35 -0
  17. dhisana-0.0.1.dev9/src/dhisana/workflow/__init__.py +1 -0
  18. dhisana-0.0.1.dev9/src/dhisana/workflow/task.py +60 -0
  19. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana.egg-info/PKG-INFO +3 -1
  20. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana.egg-info/SOURCES.txt +10 -0
  21. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana.egg-info/requires.txt +2 -0
  22. dhisana-0.0.1.dev7/src/dhisana/utils/tools_json.py +0 -123
  23. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/README.md +0 -0
  24. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/pyproject.toml +0 -0
  25. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/setup.cfg +0 -0
  26. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/__init__.py +0 -0
  27. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/cli/__init__.py +0 -0
  28. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/cli/cli.py +0 -0
  29. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/cli/datasets.py +0 -0
  30. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/cli/models.py +0 -0
  31. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/cli/predictions.py +0 -0
  32. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/ui/__init__.py +0 -0
  33. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/__init__.py +0 -0
  34. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  35. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  36. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  37. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  38. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  39. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana.egg-info/dependency_links.txt +0 -0
  40. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana.egg-info/entry_points.txt +0 -0
  41. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/src/dhisana.egg-info/top_level.txt +0 -0
  42. {dhisana-0.0.1.dev7 → dhisana-0.0.1.dev9}/tests/test_agent_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dhisana
3
- Version: 0.0.1.dev7
3
+ Version: 0.0.1.dev9
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
@@ -23,3 +23,5 @@ Requires-Dist: uvicorn[standard]
23
23
  Requires-Dist: aiohttp
24
24
  Requires-Dist: openapi_pydantic
25
25
  Requires-Dist: pandas
26
+ Requires-Dist: simple_salesforce
27
+ Requires-Dist: backoff
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev7',
5
+ version='0.0.1-dev9',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -22,7 +22,9 @@ setup(
22
22
  'uvicorn[standard]',
23
23
  'aiohttp',
24
24
  'openapi_pydantic',
25
- 'pandas'
25
+ 'pandas',
26
+ 'simple_salesforce',
27
+ 'backoff'
26
28
  ],
27
29
  entry_points={
28
30
  'console_scripts': [
@@ -1,11 +1,9 @@
1
- from typing import List, Dict, Any, Optional
2
-
1
+ from typing import List, Dict, Any, Optional, Union
3
2
 
4
3
  class Component:
5
4
  def to_dict(self) -> Dict[str, Any]:
6
5
  raise NotImplementedError("Must implement to_dict method.")
7
6
 
8
-
9
7
  class Header(Component):
10
8
  def __init__(self, title: str, subtitle: Optional[str] = None, logo: Optional[str] = None):
11
9
  self.title = title
@@ -22,7 +20,6 @@ class Header(Component):
22
20
  },
23
21
  }
24
22
 
25
-
26
23
  class Footer(Component):
27
24
  def __init__(self, content: str):
28
25
  self.content = content
@@ -35,7 +32,6 @@ class Footer(Component):
35
32
  },
36
33
  }
37
34
 
38
-
39
35
  class Sidebar(Component):
40
36
  def __init__(self, items: List[str]):
41
37
  self.items = items
@@ -48,19 +44,6 @@ class Sidebar(Component):
48
44
  },
49
45
  }
50
46
 
51
- class Text(Component):
52
- def __init__(self, content: str):
53
- self.content = content
54
-
55
- def to_dict(self):
56
- return {
57
- 'type': 'text',
58
- 'properties': {
59
- 'content': self.content,
60
- },
61
- }
62
-
63
-
64
47
  class MainContent(Component):
65
48
  def __init__(self, children: List[Component]):
66
49
  self.children = children
@@ -88,29 +71,39 @@ class ChatWindow(Component):
88
71
  },
89
72
  }
90
73
 
91
-
92
74
  class DataTable(Component):
93
75
  def __init__(
94
- self,
95
- columns: List[Dict[str, Any]],
96
- data_source: str,
97
- actions: Optional[List[Dict[str, Any]]] = None,
98
- ):
76
+ self,
77
+ columns: List[Dict[str, Any]],
78
+ data_source: str,
79
+ actions: Optional[List[Dict[str, Any]]] = None,
80
+ row_selection: Optional[bool] = False,
81
+ selected_row_keys: Optional[List[str]] = None,
82
+ on_selection_change: Optional[str] = None,
83
+ title: Optional[str] = None,
84
+ ):
85
+ self.title = title
99
86
  self.columns = columns
100
- self.data_source = data_source
87
+ self.data_source = data_source # Should be a reference to data in dataContext
101
88
  self.actions = actions or []
89
+ self.row_selection = row_selection
90
+ self.selected_row_keys = selected_row_keys
91
+ self.on_selection_change = on_selection_change
102
92
 
103
93
  def to_dict(self):
104
94
  return {
105
95
  'type': 'data-table',
106
96
  'properties': {
97
+ 'title': self.title,
107
98
  'columns': self.columns,
108
- 'dataSource': self.data_source,
99
+ 'dataSource': self.data_source, # Should be in the form '{{dataKey}}'
109
100
  'actions': self.actions,
101
+ 'rowSelection': self.row_selection,
102
+ 'selectedRowKeys': self.selected_row_keys,
103
+ 'onSelectionChange': self.on_selection_change,
110
104
  },
111
105
  }
112
106
 
113
-
114
107
  class Chart(Component):
115
108
  def __init__(self, chart_type: str, data_source: str, options: Optional[Dict[str, Any]] = None):
116
109
  self.chart_type = chart_type
@@ -127,7 +120,6 @@ class Chart(Component):
127
120
  },
128
121
  }
129
122
 
130
-
131
123
  class Form(Component):
132
124
  def __init__(self, children: List[Component], on_submit: List[str]):
133
125
  self.children = children
@@ -142,43 +134,51 @@ class Form(Component):
142
134
  'children': [child.to_dict() for child in self.children],
143
135
  }
144
136
 
145
-
146
137
  class FormItem(Component):
147
- def __init__(self, label: str, children: List[Component]):
138
+ def __init__(self, label: str, children: List[Component], visible: bool = True):
148
139
  self.label = label
149
140
  self.children = children
141
+ self.visible = visible
150
142
 
151
143
  def to_dict(self):
152
144
  return {
153
145
  'type': 'form-item',
154
146
  'properties': {
155
147
  'label': self.label,
148
+ 'visible': self.visible,
156
149
  },
157
150
  'children': [child.to_dict() for child in self.children],
158
151
  }
159
152
 
160
-
161
153
  class Input(Component):
162
- def __init__(self, name: str, placeholder: str = '', required: bool = False):
154
+ def __init__(self, name: str, type: str = 'text', placeholder: str = '', required: bool = False, value: str = None, checked: bool = False):
163
155
  self.name = name
156
+ self.type = type
164
157
  self.placeholder = placeholder
165
158
  self.required = required
159
+ self.value = value
160
+ self.checked = checked
166
161
 
167
162
  def to_dict(self):
168
163
  return {
169
164
  'type': 'input',
170
165
  'properties': {
171
166
  'name': self.name,
167
+ 'type': self.type,
172
168
  'placeholder': self.placeholder,
173
169
  'required': self.required,
170
+ 'value': self.value,
171
+ 'checked': self.checked,
174
172
  },
175
173
  }
176
174
 
177
-
178
175
  class TextArea(Component):
179
- def __init__(self, name: str, placeholder: str = ''):
176
+ def __init__(self, name: str, placeholder: str = '', value: str = None, required: bool = False, rows: int = 3):
180
177
  self.name = name
181
178
  self.placeholder = placeholder
179
+ self.value = value
180
+ self.required = required
181
+ self.rows = rows
182
182
 
183
183
  def to_dict(self):
184
184
  return {
@@ -186,14 +186,17 @@ class TextArea(Component):
186
186
  'properties': {
187
187
  'name': self.name,
188
188
  'placeholder': self.placeholder,
189
+ 'value': self.value,
190
+ 'required': self.required,
191
+ 'rows': self.rows,
189
192
  },
190
193
  }
191
194
 
192
-
193
195
  class Upload(Component):
194
- def __init__(self, name: str, required: bool = False):
196
+ def __init__(self, name: str, required: bool, multiple: bool = False):
195
197
  self.name = name
196
198
  self.required = required
199
+ self.multiple = multiple
197
200
 
198
201
  def to_dict(self):
199
202
  return {
@@ -201,16 +204,17 @@ class Upload(Component):
201
204
  'properties': {
202
205
  'name': self.name,
203
206
  'required': self.required,
207
+ 'multiple': self.multiple,
204
208
  },
205
209
  }
206
210
 
207
-
208
211
  class Button(Component):
209
- def __init__(self, label: str, button_type: str = 'button', disabled: bool = False, on_click: Optional[str] = None):
212
+ def __init__(self, label: str, button_type: str = 'button', disabled: bool = False, on_click: Optional[str] = None, style: Optional[Dict[str, Any]] = None):
210
213
  self.label = label
211
214
  self.button_type = button_type
212
215
  self.disabled = disabled
213
216
  self.on_click = on_click
217
+ self.style = style
214
218
 
215
219
  def to_dict(self):
216
220
  return {
@@ -220,9 +224,74 @@ class Button(Component):
220
224
  'type': self.button_type,
221
225
  'disabled': self.disabled,
222
226
  'onClick': self.on_click,
227
+ 'style': self.style,
228
+ },
229
+ }
230
+
231
+ class Text(Component):
232
+ def __init__(self, content: Union[str, List[Dict[str, Any]]]):
233
+ self.content = content
234
+
235
+ def to_dict(self):
236
+ return {
237
+ 'type': 'text',
238
+ 'properties': {
239
+ 'content': self.content,
223
240
  },
224
241
  }
225
242
 
243
+ class Select(Component):
244
+ def __init__(
245
+ self,
246
+ name: str,
247
+ options: List[Dict[str, Any]],
248
+ multiple: bool = False,
249
+ required: bool = False,
250
+ placeholder: Optional[str] = None,
251
+ label_field: Optional[str] = 'label',
252
+ value_field: Optional[str] = 'value',
253
+ on_change: Optional[str] = None,
254
+ ):
255
+ self.name = name
256
+ self.options = options
257
+ self.multiple = multiple
258
+ self.required = required
259
+ self.placeholder = placeholder
260
+ self.value_field = value_field
261
+ self.label_field = label_field
262
+ self.on_change = on_change
263
+
264
+
265
+ def to_dict(self):
266
+ return {
267
+ 'type': 'select',
268
+ 'properties': {
269
+ 'name': self.name,
270
+ 'options': self.options,
271
+ 'multiple': self.multiple,
272
+ 'required': self.required,
273
+ 'placeholder': self.placeholder,
274
+ 'labelField': self.label_field,
275
+ 'valueField': self.value_field,
276
+ 'onChange': self.on_change,
277
+ },
278
+ }
279
+
280
+ class Checkbox(Component):
281
+ def __init__(self, name: str, label: Optional[str] = None, checked: bool = False):
282
+ self.name = name
283
+ self.label = label
284
+ self.checked = checked
285
+
286
+ def to_dict(self):
287
+ return {
288
+ 'type': 'checkbox',
289
+ 'properties': {
290
+ 'name': self.name,
291
+ 'label': self.label,
292
+ 'checked': self.checked,
293
+ },
294
+ }
226
295
 
227
296
  class Tabs(Component):
228
297
  def __init__(self, children: List['Tab']):
@@ -234,7 +303,6 @@ class Tabs(Component):
234
303
  'children': [child.to_dict() for child in self.children],
235
304
  }
236
305
 
237
-
238
306
  class Tab(Component):
239
307
  def __init__(self, label: str, children: List[Component]):
240
308
  self.label = label
@@ -249,7 +317,6 @@ class Tab(Component):
249
317
  'children': [child.to_dict() for child in self.children],
250
318
  }
251
319
 
252
-
253
320
  class ModalDialog(Component):
254
321
  def __init__(
255
322
  self,
@@ -259,7 +326,7 @@ class ModalDialog(Component):
259
326
  visible: bool = False,
260
327
  on_close: Optional[str] = None,
261
328
  ):
262
- self.name = name
329
+ self.name = name
263
330
  self.title = title
264
331
  self.content = content
265
332
  self.visible = visible
@@ -277,10 +344,11 @@ class ModalDialog(Component):
277
344
  'children': [component.to_dict() for component in self.content],
278
345
  }
279
346
 
280
-
281
347
  class Page(Component):
282
- def __init__(self, name: str, path: str, components: List[Component]):
348
+ def __init__(self, name: str, label: str, main: bool, path: str, components: List[Component]):
283
349
  self.name = name
350
+ self.label = label
351
+ self.main = main
284
352
  self.path = path
285
353
  self.components = components
286
354
 
@@ -290,11 +358,12 @@ class Page(Component):
290
358
  'properties': {
291
359
  'name': self.name,
292
360
  'path': self.path,
361
+ 'label': self.label,
362
+ 'main': self.main,
293
363
  },
294
364
  'children': [component.to_dict() for component in self.components],
295
365
  }
296
366
 
297
-
298
367
  class Action:
299
368
  def __init__(
300
369
  self,
@@ -302,15 +371,25 @@ class Action:
302
371
  method: str,
303
372
  url: Optional[str] = None,
304
373
  data: Optional[Any] = None,
374
+ content_type: Optional[str] = None,
375
+ params: Optional[Dict[str, Any]] = None,
305
376
  state: Optional[str] = None,
306
- on_success: Optional[str] = None,
377
+ before_action: Optional[str] = None,
378
+ on_success: Optional[Any] = None,
379
+ on_error: Optional[Any] = None,
380
+ actions: Optional[List[Union['Action', str]]] = None,
307
381
  ):
308
382
  self.action_type = action_type
309
383
  self.method = method
310
384
  self.url = url
311
385
  self.data = data
386
+ self.content_type = content_type
387
+ self.params = params
312
388
  self.state = state
389
+ self.before_action = before_action
313
390
  self.on_success = on_success
391
+ self.on_error = on_error
392
+ self.actions = actions or []
314
393
 
315
394
  def to_dict(self):
316
395
  return {
@@ -318,8 +397,13 @@ class Action:
318
397
  'method': self.method,
319
398
  'url': self.url,
320
399
  'data': self.data,
400
+ 'contentType': self.content_type,
401
+ 'params': self.params,
321
402
  'state': self.state,
403
+ 'beforeAction': self.before_action,
322
404
  'onSuccess': self.on_success,
405
+ 'onError': self.on_error,
406
+ 'actions': [action.to_dict() if isinstance(action, Action) else action for action in self.actions],
323
407
  }
324
408
 
325
409
 
@@ -0,0 +1,48 @@
1
+ # Global List of tools that can be used in the assistant
2
+ # Only functions marked with @assistant_tool will be available in the allowed list
3
+ # This is in addition to the tools from OpenAPI Spec add to allowed tools
4
+ # These tools are loaded in the agent like below
5
+
6
+ # async def load_global_tool_function():
7
+ # # Iterate over all modules in the package
8
+ # for loader, module_name, is_pkg in pkgutil.walk_packages(global_tools.__path__):
9
+ # module = importlib.import_module(f"{global_tools.__name__}.{module_name}")
10
+ # for name in dir(module):
11
+ # func = getattr(module, name)
12
+ # if callable(func) and getattr(func, 'is_assistant_tool', False):
13
+ # GLOBAL_TOOLS_FUNCTIONS[name] = func
14
+
15
+ # Global Data Models Used for Data Extraction. These can be referenced in workflows.
16
+ GLOBAL_DATA_MODELS = []
17
+
18
+ # Global Functions used in workflows. Like CRM, Email, etc. They can be invoked as Python functions.
19
+ GLOBAL_TOOLS_FUNCTIONS = {}
20
+
21
+ # GLOBAL_TOOLS_FUNCTIONS in OPENAI function spec format
22
+ # src/dhisana/utils/agent_tools.py
23
+
24
+ # Global List of tools that can be used in the assistant
25
+ # Only functions marked with @assistant_tool will be available in the allowed list
26
+ # This is in addition to the tools from OpenAPI Spec add to allowed tools
27
+ # These tools are loaded in the agent like below
28
+
29
+ # async def load_global_tool_function():
30
+ # # Iterate over all modules in the package
31
+ # for loader, module_name, is_pkg in pkgutil.walk_packages(global_tools.__path__):
32
+ # module = importlib.import_module(f"{global_tools.__name__}.{module_name}")
33
+ # for name in dir(module):
34
+ # func = getattr(module, name)
35
+ # if callable(func) and getattr(func, 'is_assistant_tool', False):
36
+ # GLOBAL_TOOLS_FUNCTIONS[name] = func
37
+
38
+ # Ensure GLOBAL_DATA_MODELS is only initialized once
39
+ if 'GLOBAL_DATA_MODELS' not in globals():
40
+ GLOBAL_DATA_MODELS = []
41
+
42
+ # Ensure GLOBAL_TOOLS_FUNCTIONS is only initialized once
43
+ if 'GLOBAL_TOOLS_FUNCTIONS' not in globals():
44
+ GLOBAL_TOOLS_FUNCTIONS = {}
45
+
46
+ # Ensure GLOBAL_OPENAI_ASSISTANT_TOOLS is only initialized once
47
+ if 'GLOBAL_OPENAI_ASSISTANT_TOOLS' not in globals():
48
+ GLOBAL_OPENAI_ASSISTANT_TOOLS = []
@@ -0,0 +1,121 @@
1
+ import os
2
+ import aiohttp
3
+ from dhisana.utils.assistant_tool_tag import assistant_tool
4
+ from typing import List, Optional, Union
5
+ import backoff
6
+
7
+ @assistant_tool
8
+ @backoff.on_exception(
9
+ backoff.expo,
10
+ aiohttp.ClientResponseError,
11
+ max_tries=2,
12
+ giveup=lambda e: e.status != 429,
13
+ factor=10,
14
+ )
15
+ async def enrich_person_info_from_apollo(
16
+ linkedin_url: Optional[str] = None,
17
+ email: Optional[str] = None,
18
+ phone: Optional[str] = None,
19
+ ):
20
+ """
21
+ Fetch a person's details from Apollo using LinkedIn URL, email, or phone number.
22
+
23
+ Parameters:
24
+ - **linkedin_url** (*str*, optional): LinkedIn profile URL of the person.
25
+ - **email** (*str*, optional): Email address of the person.
26
+ - **phone** (*str*, optional): Phone number of the person.
27
+
28
+ Returns:
29
+ - **dict**: JSON response containing person information.
30
+ """
31
+ APOLLO_API_KEY = os.environ.get('APOLLO_API_KEY')
32
+ if not APOLLO_API_KEY:
33
+ return {'error': "Apollo API key not found in environment variables"}
34
+
35
+ if not linkedin_url and not email and not phone:
36
+ return {'error': "At least one of linkedin_url, email, or phone must be provided"}
37
+
38
+ headers = {
39
+ "X-Api-Key": f"{APOLLO_API_KEY}",
40
+ "Content-Type": "application/json"
41
+ }
42
+
43
+ data = {}
44
+ if linkedin_url:
45
+ data['linkedin_url'] = linkedin_url
46
+ if email:
47
+ data['email'] = email
48
+ if phone:
49
+ data['phone_numbers'] = [phone] # Apollo expects a list for phone numbers
50
+
51
+ url = 'https://api.apollo.io/v1/people/match' # Correct API endpoint
52
+
53
+ async with aiohttp.ClientSession() as session:
54
+ async with session.post(url, headers=headers, json=data) as response:
55
+ if response.status == 200:
56
+ return await response.json()
57
+ elif response.status == 429:
58
+ raise aiohttp.ClientResponseError(
59
+ request_info=response.request_info,
60
+ history=response.history,
61
+ status=response.status,
62
+ message="Rate limit exceeded",
63
+ headers=response.headers
64
+ )
65
+ else:
66
+ result = await response.json()
67
+ return {'error': result}
68
+
69
+
70
+
71
+ @assistant_tool
72
+ @backoff.on_exception(
73
+ backoff.expo,
74
+ aiohttp.ClientResponseError,
75
+ max_tries=2,
76
+ giveup=lambda e: e.status != 429,
77
+ factor=10,
78
+ )
79
+ async def enrich_company_info_from_apollo(
80
+ company_domain: Optional[str] = None,
81
+ ):
82
+ """
83
+ Fetch a company's details from Apollo using the company domain.
84
+
85
+ Parameters:
86
+ - **company_domain** (*str*, optional): Domain of the company.
87
+
88
+ Returns:
89
+ - **dict**: JSON response containing company information.
90
+ """
91
+ APOLLO_API_KEY = os.environ.get('APOLLO_API_KEY')
92
+ if not APOLLO_API_KEY:
93
+ return {'error': "Apollo API key not found in environment variables"}
94
+
95
+ if not company_domain:
96
+ return {'error': "Company domain must be provided"}
97
+
98
+ headers = {
99
+ "X-Api-Key": f"{APOLLO_API_KEY}",
100
+ "Content-Type": "application/json",
101
+ "Cache-Control": "no-cache",
102
+ "accept": "application/json"
103
+ }
104
+
105
+ url = f'https://api.apollo.io/api/v1/organizations/enrich?domain={company_domain}'
106
+
107
+ async with aiohttp.ClientSession() as session:
108
+ async with session.get(url, headers=headers) as response:
109
+ if response.status == 200:
110
+ return await response.json()
111
+ elif response.status == 429:
112
+ raise aiohttp.ClientResponseError(
113
+ request_info=response.request_info,
114
+ history=response.history,
115
+ status=response.status,
116
+ message="Rate limit exceeded",
117
+ headers=response.headers
118
+ )
119
+ else:
120
+ result = await response.json()
121
+ return {'error': result}
@@ -0,0 +1,36 @@
1
+ # Check validity of email
2
+
3
+ import os
4
+ import os
5
+ import aiohttp
6
+ from dhisana.utils.assistant_tool_tag import assistant_tool
7
+
8
+ @assistant_tool
9
+ async def check_email_validity_with_zero_bounce(email_id: str):
10
+ """
11
+ Validate a single email address using the ZeroBounce API.
12
+
13
+ This function sends an asynchronous GET request to the ZeroBounce API to validate the provided email address.
14
+
15
+ Parameters:
16
+ email_id (str): The email address to be validated.
17
+
18
+ Returns:
19
+ dict: The JSON response from the ZeroBounce API containing the validation results.
20
+
21
+ Raises:
22
+ ValueError: If the ZeroBounce API key is not found in the environment variables.
23
+ Exception: If the response status code from the ZeroBounce API is not 200.
24
+ """
25
+ ZERO_BOUNCE_API_KEY = os.environ.get('ZERO_BOUNCE_API_KEY')
26
+ if not ZERO_BOUNCE_API_KEY:
27
+ raise ValueError("ZeroBounce API key not found in environment variables")
28
+
29
+ url = f"https://api.zerobounce.net/v2/validate?api_key={ZERO_BOUNCE_API_KEY}&email={email_id}"
30
+
31
+ async with aiohttp.ClientSession() as session:
32
+ async with session.get(url) as response:
33
+ if response.status != 200:
34
+ raise Exception(f"Error: Received status code {response.status}")
35
+ result = await response.json()
36
+ return result