dhisana 0.0.1.dev218__py3-none-any.whl → 0.0.1.dev220__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dhisana/schemas/sales.py CHANGED
@@ -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):
@@ -11,7 +11,7 @@ from dhisana.schemas.sales import LeadsQueryFilters, CompanyQueryFilters
11
11
  from dhisana.utils.cache_output_tools import cache_output, retrieve_output
12
12
  from dhisana.utils.assistant_tool_tag import assistant_tool
13
13
  from urllib.parse import urlparse, parse_qs
14
- from typing import Any, Dict, List, Optional, Union
14
+ from typing import Any, Dict, List, Optional, Tuple, Union
15
15
 
16
16
  from dhisana.utils.clean_properties import cleanup_properties
17
17
 
@@ -19,50 +19,81 @@ logging.basicConfig(level=logging.INFO)
19
19
  logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
- def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> str:
22
+ def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> Tuple[str, bool]:
23
23
  """
24
- Retrieves the APOLLO_API_KEY access token from the provided tool configuration.
24
+ Retrieves an Apollo access token from tool configuration or environment variables.
25
25
 
26
26
  Args:
27
- tool_config (list): A list of dictionaries containing the tool configuration.
28
- Each dictionary should have a "name" key and a "configuration" key,
29
- where "configuration" is a list of dictionaries containing "name" and "value" keys.
27
+ tool_config (list): Optional tool configuration payload provided to the tool.
30
28
 
31
29
  Returns:
32
- str: The APOLLO_API_KEY access token.
30
+ Tuple[str, bool]: A tuple containing the token string and a boolean flag indicating
31
+ whether the token represents an OAuth bearer token (``True``) or an API key (``False``).
33
32
 
34
33
  Raises:
35
34
  ValueError: If the Apollo integration has not been configured.
36
35
  """
37
- APOLLO_API_KEY = None
36
+ token: Optional[str] = None
37
+ is_oauth = False
38
38
 
39
39
  if tool_config:
40
- logger.debug(f"Tool config provided: {tool_config}")
41
40
  apollo_config = next(
42
41
  (item for item in tool_config if item.get("name") == "apollo"), None
43
42
  )
44
43
  if apollo_config:
45
44
  config_map = {
46
- item["name"]: item["value"]
45
+ item["name"]: item.get("value")
47
46
  for item in apollo_config.get("configuration", [])
48
47
  if item
49
48
  }
50
- APOLLO_API_KEY = config_map.get("apiKey")
49
+
50
+ raw_oauth = config_map.get("oauth_tokens")
51
+ if isinstance(raw_oauth, str):
52
+ try:
53
+ raw_oauth = json.loads(raw_oauth)
54
+ except Exception:
55
+ raw_oauth = None
56
+ if isinstance(raw_oauth, dict):
57
+ token = (
58
+ raw_oauth.get("access_token")
59
+ or raw_oauth.get("token")
60
+ )
61
+ if token:
62
+ is_oauth = True
63
+
64
+ if not token:
65
+ direct_access_token = config_map.get("access_token")
66
+ if direct_access_token:
67
+ token = direct_access_token
68
+ is_oauth = True
69
+
70
+ if not token:
71
+ api_key = config_map.get("apiKey") or config_map.get("api_key")
72
+ if api_key:
73
+ token = api_key
74
+ is_oauth = False
51
75
  else:
52
76
  logger.warning("No 'apollo' config item found in tool_config.")
53
- else:
54
- logger.debug("No tool_config provided or it's None.")
55
77
 
56
- # Check environment variable if no key found yet
57
- APOLLO_API_KEY = APOLLO_API_KEY or os.getenv("APOLLO_API_KEY")
78
+ if not token:
79
+ env_oauth_token = os.getenv("APOLLO_ACCESS_TOKEN")
80
+ if env_oauth_token:
81
+ token = env_oauth_token
82
+ is_oauth = True
83
+
84
+ if not token:
85
+ env_api_key = os.getenv("APOLLO_API_KEY")
86
+ if env_api_key:
87
+ token = env_api_key
88
+ is_oauth = False
58
89
 
59
- if not APOLLO_API_KEY:
90
+ if not token:
60
91
  logger.error("Apollo integration is not configured.")
61
92
  raise ValueError(
62
93
  "Apollo integration is not configured. Please configure the connection to Apollo in Integrations."
63
94
  )
64
95
 
65
- return APOLLO_API_KEY
96
+ return token, is_oauth
66
97
 
67
98
 
68
99
  @assistant_tool
@@ -94,16 +125,17 @@ async def enrich_person_info_from_apollo(
94
125
  """
95
126
  logger.info("Entering enrich_person_info_from_apollo")
96
127
 
97
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
128
+ token, is_oauth = get_apollo_access_token(tool_config)
98
129
 
99
130
  if not linkedin_url and not email and not phone:
100
131
  logger.warning("No linkedin_url, email, or phone provided. At least one is required.")
101
132
  return {'error': "At least one of linkedin_url, email, or phone must be provided"}
102
133
 
103
- headers = {
104
- "X-Api-Key": f"{APOLLO_API_KEY}",
105
- "Content-Type": "application/json"
106
- }
134
+ headers = {"Content-Type": "application/json"}
135
+ if is_oauth:
136
+ headers["Authorization"] = f"Bearer {token}"
137
+ else:
138
+ headers["X-Api-Key"] = token
107
139
 
108
140
  data = {}
109
141
  if linkedin_url:
@@ -186,11 +218,12 @@ async def lookup_person_in_apollo_by_name(
186
218
  logger.warning("No full_name provided.")
187
219
  return {'error': "Full name is required"}
188
220
 
189
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
190
- headers = {
191
- "X-Api-Key": f"{APOLLO_API_KEY}",
192
- "Content-Type": "application/json"
193
- }
221
+ token, is_oauth = get_apollo_access_token(tool_config)
222
+ headers = {"Content-Type": "application/json"}
223
+ if is_oauth:
224
+ headers["Authorization"] = f"Bearer {token}"
225
+ else:
226
+ headers["X-Api-Key"] = token
194
227
 
195
228
  # Construct the query payload
196
229
  data = {
@@ -263,18 +296,21 @@ async def enrich_organization_info_from_apollo(
263
296
  """
264
297
  logger.info("Entering enrich_organization_info_from_apollo")
265
298
 
266
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
299
+ token, is_oauth = get_apollo_access_token(tool_config)
267
300
 
268
301
  if not organization_domain:
269
302
  logger.warning("No organization domain provided.")
270
303
  return {'error': "organization domain must be provided"}
271
304
 
272
305
  headers = {
273
- "X-Api-Key": f"{APOLLO_API_KEY}",
274
306
  "Content-Type": "application/json",
275
307
  "Cache-Control": "no-cache",
276
308
  "accept": "application/json"
277
309
  }
310
+ if is_oauth:
311
+ headers["Authorization"] = f"Bearer {token}"
312
+ else:
313
+ headers["X-Api-Key"] = token
278
314
 
279
315
  cached_response = retrieve_output("enrich_organization_info_from_apollo", organization_domain)
280
316
  if cached_response is not None:
@@ -364,12 +400,15 @@ async def search_people_with_apollo(
364
400
  logger.warning("No payload given; returning empty result.")
365
401
  return []
366
402
 
367
- api_key = get_apollo_access_token(tool_config)
403
+ token, is_oauth = get_apollo_access_token(tool_config)
368
404
  headers = {
369
405
  "Cache-Control": "no-cache",
370
406
  "Content-Type": "application/json",
371
- "X-Api-Key": api_key,
372
407
  }
408
+ if is_oauth:
409
+ headers["Authorization"] = f"Bearer {token}"
410
+ else:
411
+ headers["X-Api-Key"] = token
373
412
 
374
413
  url = "https://api.apollo.io/api/v1/mixed_people/search"
375
414
  logger.info(f"Sending payload to Apollo (single page): {json.dumps(dynamic_payload, indent=2)}")
@@ -840,12 +879,15 @@ async def search_leads_with_apollo_page(
840
879
  f" Payload: {json.dumps(page_payload, indent=2)}")
841
880
 
842
881
  # Get the full Apollo API response with pagination metadata
843
- api_key = get_apollo_access_token(tool_config)
882
+ token, is_oauth = get_apollo_access_token(tool_config)
844
883
  headers = {
845
884
  "Cache-Control": "no-cache",
846
885
  "Content-Type": "application/json",
847
- "X-Api-Key": api_key,
848
886
  }
887
+ if is_oauth:
888
+ headers["Authorization"] = f"Bearer {token}"
889
+ else:
890
+ headers["X-Api-Key"] = token
849
891
 
850
892
  url = "https://api.apollo.io/api/v1/mixed_people/search"
851
893
 
@@ -950,17 +992,20 @@ async def get_organization_details_from_apollo(
950
992
  """
951
993
  logger.info("Entering get_organization_details_from_apollo")
952
994
 
953
- APOLLO_API_KEY = get_apollo_access_token(tool_config)
995
+ token, is_oauth = get_apollo_access_token(tool_config)
954
996
  if not organization_id:
955
997
  logger.warning("No organization_id provided.")
956
998
  return {'error': "Organization ID must be provided"}
957
999
 
958
1000
  headers = {
959
- "X-Api-Key": APOLLO_API_KEY,
960
1001
  "Content-Type": "application/json",
961
1002
  "Cache-Control": "no-cache",
962
1003
  "Accept": "application/json"
963
1004
  }
1005
+ if is_oauth:
1006
+ headers["Authorization"] = f"Bearer {token}"
1007
+ else:
1008
+ headers["X-Api-Key"] = token
964
1009
 
965
1010
  cached_response = retrieve_output("get_organization_details_from_apollo", organization_id)
966
1011
  if cached_response is not None:
@@ -1202,12 +1247,15 @@ async def search_companies_with_apollo(
1202
1247
  logger.warning("No payload given; returning empty result.")
1203
1248
  return []
1204
1249
 
1205
- api_key = get_apollo_access_token(tool_config)
1250
+ token, is_oauth = get_apollo_access_token(tool_config)
1206
1251
  headers = {
1207
1252
  "Cache-Control": "no-cache",
1208
1253
  "Content-Type": "application/json",
1209
- "X-Api-Key": api_key,
1210
1254
  }
1255
+ if is_oauth:
1256
+ headers["Authorization"] = f"Bearer {token}"
1257
+ else:
1258
+ headers["X-Api-Key"] = token
1211
1259
 
1212
1260
  url = "https://api.apollo.io/api/v1/organizations/search"
1213
1261
  logger.info(f"Sending payload to Apollo organizations endpoint (single page): {json.dumps(dynamic_payload, indent=2)}")
@@ -1508,12 +1556,15 @@ async def search_companies_with_apollo_page(
1508
1556
  f" Payload: {json.dumps(cleaned_payload, indent=2)}")
1509
1557
 
1510
1558
  # Get the full Apollo API response with pagination metadata
1511
- api_key = get_apollo_access_token(tool_config)
1559
+ token, is_oauth = get_apollo_access_token(tool_config)
1512
1560
  headers = {
1513
1561
  "Cache-Control": "no-cache",
1514
1562
  "Content-Type": "application/json",
1515
- "X-Api-Key": api_key,
1516
1563
  }
1564
+ if is_oauth:
1565
+ headers["Authorization"] = f"Bearer {token}"
1566
+ else:
1567
+ headers["X-Api-Key"] = token
1517
1568
 
1518
1569
  url = "https://api.apollo.io/api/v1/organizations/search"
1519
1570
 
@@ -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.dev220
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
@@ -6,13 +6,13 @@ dhisana/cli/models.py,sha256=IzUFZW_X32mL3fpM1_j4q8AF7v5nrxJcxBoqvG-TTgA,706
6
6
  dhisana/cli/predictions.py,sha256=VYgoLK1Ksv6MFImoYZqjQJkds7e5Hso65dHwbxTNNzE,646
7
7
  dhisana/schemas/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
8
8
  dhisana/schemas/common.py,sha256=WErBbyZAQtkmSOXrMkamJkMptj3uCY1S2CGHJWkB_Ps,9103
9
- dhisana/schemas/sales.py,sha256=CnBi6_YZwqfcd_vDERoMoW8QAa1yfkcOWbFHVr1UcPA,31936
9
+ dhisana/schemas/sales.py,sha256=2fDcjGJEAXi7bOkOmdJ14lqt3pdy3GWA28PFn6yMYQE,33354
10
10
  dhisana/ui/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
11
11
  dhisana/ui/components.py,sha256=4NXrAyl9tx2wWwoVYyABO-EOGnreGMvql1AkXWajIIo,14316
12
12
  dhisana/utils/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
13
13
  dhisana/utils/add_mapping.py,sha256=oq_QNqag86DhgdwINBRRXNx7SOb8Q9M-V0QLP6pTzr8,13837
14
14
  dhisana/utils/agent_tools.py,sha256=pzBFvfhU4wfSB4zv1eiRzjmnteJnfhC5V32r_v1m38Y,2321
15
- dhisana/utils/apollo_tools.py,sha256=no-PwDVtPfOSDr6eZ-cN5C3C_Yf2SeElft5ksap24Gg,64136
15
+ dhisana/utils/apollo_tools.py,sha256=WSt5SV3ElfYlNjFw6-8FyZ0_6wete5vahoSYPhACcD8,65716
16
16
  dhisana/utils/assistant_tool_tag.py,sha256=rYRl8ubLI7fUUIjg30XTefHBkFgRqNEVC12lF6U6Z-8,119
17
17
  dhisana/utils/built_with_api_tools.py,sha256=TFNGhnPb2vFdveVCpjiCvE1WKe_eK95UPpR0Ha5NgMQ,10260
18
18
  dhisana/utils/cache_output_tools.py,sha256=sSAruvUZn-WAJQ0lB9T1QjSmkm-_14AuxC9xKmcCQ0k,3428
@@ -78,7 +78,7 @@ dhisana/utils/serperdev_google_jobs.py,sha256=m5_2f_5y79FOFZz1A_go6m0hIUfbbAoZ0Y
78
78
  dhisana/utils/serperdev_local_business.py,sha256=JoZfTg58Hojv61cyuwA2lcnPdLT1lawnWaBNrUYWnuQ,6447
79
79
  dhisana/utils/serperdev_search.py,sha256=_iBKIfHMq4gFv5StYz58eArriygoi1zW6VnLlux8vto,9363
80
80
  dhisana/utils/smtp_email_tools.py,sha256=kx0VSa0JQyVLD020oARCHhOBZJxDwMIri1Z7jPN0nP4,15843
81
- dhisana/utils/test_connect.py,sha256=-BL0BfHsKuD9JMAfKzyWyLolcZomYlKQWYdkQC8TOn8,53970
81
+ dhisana/utils/test_connect.py,sha256=1rupW6dIfRzBKUH1AcWqfhYWhW-wETD475AJlCbgvFw,58254
82
82
  dhisana/utils/trasform_json.py,sha256=s48DoyzVVCI4dTvSUwF5X-exX6VH6nPWrjbUENkYuNE,6979
83
83
  dhisana/utils/web_download_parse_tools.py,sha256=ouXwH7CmjcRjoBfP5BWat86MvcGO-8rLCmWQe_eZKjc,7810
84
84
  dhisana/utils/workflow_code_model.py,sha256=YPWse5vBb3O6Km2PvKh1Q3AB8qBkzLt1CrR5xOL9Mro,99
@@ -92,8 +92,8 @@ dhisana/workflow/agent.py,sha256=esv7_i_XuMkV2j1nz_UlsHov_m6X5WZZiZm_tG4OBHU,565
92
92
  dhisana/workflow/flow.py,sha256=xWE3qQbM7j2B3FH8XnY3zOL_QXX4LbTW4ArndnEYJE0,1638
93
93
  dhisana/workflow/task.py,sha256=HlWz9mtrwLYByoSnePOemBUBrMEcj7KbgNjEE1oF5wo,1830
94
94
  dhisana/workflow/test.py,sha256=kwW8jWqSBNcRmoyaxlTuZCMOpGJpTbJQgHI7gSjwdzM,3399
95
- dhisana-0.0.1.dev218.dist-info/METADATA,sha256=mF4K1TD6zfcNFZCXN1-YvFesMEKFO4txQyBOmmRb_v4,1190
96
- dhisana-0.0.1.dev218.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
97
- dhisana-0.0.1.dev218.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
98
- dhisana-0.0.1.dev218.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
99
- dhisana-0.0.1.dev218.dist-info/RECORD,,
95
+ dhisana-0.0.1.dev220.dist-info/METADATA,sha256=HsH4PGLIF-9i_HFv9ODch9lhyl7tdQ48epFKP7TcUXM,1190
96
+ dhisana-0.0.1.dev220.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
97
+ dhisana-0.0.1.dev220.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
98
+ dhisana-0.0.1.dev220.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
99
+ dhisana-0.0.1.dev220.dist-info/RECORD,,