dhisana 0.0.1.dev234__py3-none-any.whl → 0.0.1.dev235__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/common.py +1 -0
- dhisana/utils/google_oauth_tools.py +37 -7
- dhisana/utils/google_workspace_tools.py +34 -8
- dhisana/utils/microsoft365_tools.py +43 -3
- dhisana/utils/smtp_email_tools.py +103 -10
- {dhisana-0.0.1.dev234.dist-info → dhisana-0.0.1.dev235.dist-info}/METADATA +1 -1
- {dhisana-0.0.1.dev234.dist-info → dhisana-0.0.1.dev235.dist-info}/RECORD +10 -10
- {dhisana-0.0.1.dev234.dist-info → dhisana-0.0.1.dev235.dist-info}/WHEEL +0 -0
- {dhisana-0.0.1.dev234.dist-info → dhisana-0.0.1.dev235.dist-info}/entry_points.txt +0 -0
- {dhisana-0.0.1.dev234.dist-info → dhisana-0.0.1.dev235.dist-info}/top_level.txt +0 -0
dhisana/schemas/common.py
CHANGED
|
@@ -392,6 +392,7 @@ class ReplyEmailContext(BaseModel):
|
|
|
392
392
|
reply_body: str
|
|
393
393
|
sender_email: str
|
|
394
394
|
sender_name: str
|
|
395
|
+
fallback_recipient: Optional[str] = None
|
|
395
396
|
mark_as_read: str = "True"
|
|
396
397
|
add_labels: Optional[List[str]] = None
|
|
397
398
|
reply_body_format: BodyFormat = BodyFormat.AUTO
|
|
@@ -267,15 +267,45 @@ async def reply_to_email_google_oauth_async(
|
|
|
267
267
|
_rethrow_with_google_message(exc, "Gmail Fetch Message OAuth")
|
|
268
268
|
|
|
269
269
|
headers_list = (original.get("payload") or {}).get("headers", [])
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
subject = headers_map.get("Subject", "") or ""
|
|
270
|
+
# Use case-insensitive lookups via find_header to avoid missing values on header casing differences.
|
|
271
|
+
subject = find_header(headers_list, "Subject") or ""
|
|
274
272
|
if not subject.startswith("Re:"):
|
|
275
273
|
subject = f"Re: {subject}"
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
reply_to_header = find_header(headers_list, "Reply-To") or ""
|
|
275
|
+
from_header = find_header(headers_list, "From") or ""
|
|
276
|
+
to_header = find_header(headers_list, "To") or ""
|
|
277
|
+
cc_header = find_header(headers_list, "Cc") or ""
|
|
278
|
+
message_id_header = find_header(headers_list, "Message-ID") or ""
|
|
279
|
+
thread_id = original.get("threadId")
|
|
280
|
+
|
|
281
|
+
sender_email_lc = (reply_email_context.sender_email or "").lower()
|
|
282
|
+
|
|
283
|
+
def _is_self(addr: str) -> bool:
|
|
284
|
+
return bool(sender_email_lc) and sender_email_lc in addr.lower()
|
|
285
|
+
|
|
286
|
+
cc_addresses = cc_header or ""
|
|
287
|
+
# Prefer Reply-To unless it points back to the sender. If the original was SENT mail,
|
|
288
|
+
# From will equal the sender, so we should reply to the original To/CC instead.
|
|
289
|
+
if reply_to_header and not _is_self(reply_to_header):
|
|
290
|
+
to_addresses = reply_to_header
|
|
291
|
+
elif from_header and not _is_self(from_header):
|
|
292
|
+
to_addresses = from_header
|
|
293
|
+
elif to_header and not _is_self(to_header):
|
|
294
|
+
to_addresses = to_header
|
|
295
|
+
else:
|
|
296
|
+
combined = ", ".join([v for v in (to_header, cc_header, from_header) if v])
|
|
297
|
+
to_addresses = combined
|
|
298
|
+
cc_addresses = ""
|
|
299
|
+
|
|
300
|
+
if (not to_addresses or _is_self(to_addresses)) and reply_email_context.fallback_recipient:
|
|
301
|
+
if not _is_self(reply_email_context.fallback_recipient):
|
|
302
|
+
to_addresses = reply_email_context.fallback_recipient
|
|
303
|
+
cc_addresses = ""
|
|
304
|
+
|
|
305
|
+
if not to_addresses or _is_self(to_addresses):
|
|
306
|
+
raise ValueError(
|
|
307
|
+
"No valid recipient found in the original message; refusing to reply to sender."
|
|
308
|
+
)
|
|
279
309
|
|
|
280
310
|
# 2) Build reply MIME
|
|
281
311
|
plain_reply, html_reply, resolved_reply_fmt = body_variants(
|
|
@@ -895,17 +895,43 @@ async def reply_to_email_async(
|
|
|
895
895
|
original_message = response.json()
|
|
896
896
|
|
|
897
897
|
headers_list = original_message.get('payload', {}).get('headers', [])
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
# 2. Prepare reply headers
|
|
902
|
-
subject = headers_dict.get('Subject', '')
|
|
898
|
+
# Case-insensitive header lookup and resilient recipient fallback to avoid Gmail 400s.
|
|
899
|
+
subject = find_header(headers_list, 'Subject') or ''
|
|
903
900
|
if not subject.startswith('Re:'):
|
|
904
901
|
subject = f'Re: {subject}'
|
|
902
|
+
reply_to_header = find_header(headers_list, 'Reply-To') or ''
|
|
903
|
+
from_header = find_header(headers_list, 'From') or ''
|
|
904
|
+
to_header = find_header(headers_list, 'To') or ''
|
|
905
|
+
cc_header = find_header(headers_list, 'Cc') or ''
|
|
906
|
+
message_id_header = find_header(headers_list, 'Message-ID') or ''
|
|
907
|
+
thread_id = original_message.get('threadId')
|
|
908
|
+
|
|
909
|
+
sender_email_lc = (reply_email_context.sender_email or '').lower()
|
|
910
|
+
|
|
911
|
+
def _is_self(addr: str) -> bool:
|
|
912
|
+
return bool(sender_email_lc) and sender_email_lc in addr.lower()
|
|
905
913
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
914
|
+
cc_addresses = cc_header or ''
|
|
915
|
+
if reply_to_header and not _is_self(reply_to_header):
|
|
916
|
+
to_addresses = reply_to_header
|
|
917
|
+
elif from_header and not _is_self(from_header):
|
|
918
|
+
to_addresses = from_header
|
|
919
|
+
elif to_header and not _is_self(to_header):
|
|
920
|
+
to_addresses = to_header
|
|
921
|
+
else:
|
|
922
|
+
combined = ", ".join([v for v in (to_header, cc_header, from_header) if v])
|
|
923
|
+
to_addresses = combined
|
|
924
|
+
cc_addresses = ''
|
|
925
|
+
|
|
926
|
+
if (not to_addresses or _is_self(to_addresses)) and reply_email_context.fallback_recipient:
|
|
927
|
+
if not _is_self(reply_email_context.fallback_recipient):
|
|
928
|
+
to_addresses = reply_email_context.fallback_recipient
|
|
929
|
+
cc_addresses = ''
|
|
930
|
+
|
|
931
|
+
if not to_addresses or _is_self(to_addresses):
|
|
932
|
+
raise ValueError(
|
|
933
|
+
"No valid recipient found in the original message; refusing to reply to sender."
|
|
934
|
+
)
|
|
909
935
|
|
|
910
936
|
# 3. Create the reply email message
|
|
911
937
|
plain_reply, html_reply, resolved_reply_fmt = body_variants(
|
|
@@ -351,10 +351,50 @@ async def reply_to_email_m365_async(
|
|
|
351
351
|
orig_subject = orig.get("subject", "")
|
|
352
352
|
subject = orig_subject if orig_subject.startswith("Re:") else f"Re: {orig_subject}"
|
|
353
353
|
thread_id = orig.get("conversationId", "")
|
|
354
|
-
from_addr = orig.get("from", {}).get("emailAddress", {})
|
|
355
|
-
to_addresses = from_addr.get("address", "")
|
|
356
354
|
cc_list = orig.get("ccRecipients", [])
|
|
357
|
-
|
|
355
|
+
to_list = orig.get("toRecipients", [])
|
|
356
|
+
sender_email_lc = (reply_email_context.sender_email or "").lower()
|
|
357
|
+
|
|
358
|
+
def _is_self(addr: str) -> bool:
|
|
359
|
+
return bool(sender_email_lc) and sender_email_lc in addr.lower()
|
|
360
|
+
|
|
361
|
+
def _addresses(recipients: List[Dict[str, Any]]) -> List[str]:
|
|
362
|
+
return [
|
|
363
|
+
(recipient.get("emailAddress", {}) or {}).get("address", "")
|
|
364
|
+
for recipient in recipients
|
|
365
|
+
if recipient
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
to_addresses = ", ".join(
|
|
369
|
+
[addr for addr in _addresses(to_list) if addr and not _is_self(addr)]
|
|
370
|
+
)
|
|
371
|
+
cc_addresses = ", ".join(
|
|
372
|
+
[addr for addr in _addresses(cc_list) if addr and not _is_self(addr)]
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
all_recipients = [addr for addr in _addresses(to_list + cc_list) if addr]
|
|
376
|
+
if not any(all_recipients):
|
|
377
|
+
from_addr = orig.get("from", {}).get("emailAddress", {})
|
|
378
|
+
from_address = from_addr.get("address", "")
|
|
379
|
+
if from_address:
|
|
380
|
+
all_recipients.append(from_address)
|
|
381
|
+
|
|
382
|
+
non_self_recipients = [addr for addr in all_recipients if not _is_self(addr)]
|
|
383
|
+
if not non_self_recipients and reply_email_context.fallback_recipient:
|
|
384
|
+
fr = reply_email_context.fallback_recipient
|
|
385
|
+
if fr and not _is_self(fr):
|
|
386
|
+
non_self_recipients.append(fr)
|
|
387
|
+
|
|
388
|
+
if not to_addresses and non_self_recipients:
|
|
389
|
+
to_addresses = ", ".join(non_self_recipients)
|
|
390
|
+
cc_addresses = ""
|
|
391
|
+
|
|
392
|
+
if not non_self_recipients:
|
|
393
|
+
raise httpx.HTTPStatusError(
|
|
394
|
+
"No valid recipient found in the original message; refusing to reply to sender.",
|
|
395
|
+
request=get_resp.request,
|
|
396
|
+
response=get_resp,
|
|
397
|
+
)
|
|
358
398
|
|
|
359
399
|
# 2) Create reply-all draft with comment
|
|
360
400
|
create_reply_url = (
|
|
@@ -394,17 +394,99 @@ async def reply_to_email_via_smtp_async(
|
|
|
394
394
|
)
|
|
395
395
|
try:
|
|
396
396
|
conn.login(username, password)
|
|
397
|
-
|
|
398
|
-
#
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
397
|
+
# Sent messages usually live outside INBOX; build a candidate list
|
|
398
|
+
# from the provided mailbox, common sent folders, and any LISTed
|
|
399
|
+
# mailboxes containing "sent" (case-insensitive).
|
|
400
|
+
candidate_mailboxes = []
|
|
401
|
+
if mailbox:
|
|
402
|
+
candidate_mailboxes.append(mailbox)
|
|
403
|
+
candidate_mailboxes.extend([
|
|
404
|
+
"Sent",
|
|
405
|
+
"Sent Items",
|
|
406
|
+
"Sent Mail",
|
|
407
|
+
"[Gmail]/Sent Mail",
|
|
408
|
+
"[Gmail]/Sent Items",
|
|
409
|
+
"INBOX.Sent",
|
|
410
|
+
"INBOX/Sent",
|
|
411
|
+
])
|
|
412
|
+
try:
|
|
413
|
+
status, mailboxes = conn.list()
|
|
414
|
+
if status == "OK" and mailboxes:
|
|
415
|
+
for mbox in mailboxes:
|
|
416
|
+
try:
|
|
417
|
+
decoded = mbox.decode(errors="ignore")
|
|
418
|
+
except Exception:
|
|
419
|
+
decoded = str(mbox)
|
|
420
|
+
# Parse flags + name from LIST response:
|
|
421
|
+
# e.g., (\\HasNoChildren \\Sent) "/" "Sent Items"
|
|
422
|
+
flags = set()
|
|
423
|
+
name_part = decoded
|
|
424
|
+
if ") " in decoded:
|
|
425
|
+
flags_raw, _, remainder = decoded.partition(") ")
|
|
426
|
+
flags = {f.lower() for f in flags_raw.strip("(").split() if f}
|
|
427
|
+
# remainder is like '"/" "Sent Items"' or '"/" Sent'
|
|
428
|
+
pieces = remainder.split(" ", 1)
|
|
429
|
+
if len(pieces) == 2:
|
|
430
|
+
name_part = pieces[1].strip()
|
|
431
|
+
else:
|
|
432
|
+
name_part = remainder.strip()
|
|
433
|
+
name_part = name_part.strip()
|
|
434
|
+
if name_part.startswith('"') and name_part.endswith('"'):
|
|
435
|
+
name_part = name_part[1:-1]
|
|
436
|
+
|
|
437
|
+
# Prefer provider-marked \Sent flag; otherwise fall back to substring match.
|
|
438
|
+
is_sent_flag = "\\sent" in flags
|
|
439
|
+
is_sent_name = "sent" in name_part.lower()
|
|
440
|
+
if is_sent_flag or is_sent_name:
|
|
441
|
+
candidate_mailboxes.append(name_part)
|
|
442
|
+
except Exception:
|
|
443
|
+
logging.exception("IMAP LIST failed; continuing with default sent folders")
|
|
444
|
+
# Deduplicate while preserving order
|
|
445
|
+
seen = set()
|
|
446
|
+
candidate_mailboxes = [m for m in candidate_mailboxes if not (m in seen or seen.add(m))]
|
|
447
|
+
|
|
448
|
+
msg_data = None
|
|
449
|
+
for mb in candidate_mailboxes:
|
|
450
|
+
def _try_select(name: str) -> bool:
|
|
451
|
+
# Quote mailbox names with spaces or special chars; fall back to raw.
|
|
452
|
+
for candidate in (f'"{name}"', name):
|
|
453
|
+
try:
|
|
454
|
+
status, _ = conn.select(candidate, readonly=False)
|
|
455
|
+
except imaplib.IMAP4.error as exc:
|
|
456
|
+
logging.warning("IMAP select %r failed: %s", candidate, exc)
|
|
457
|
+
continue
|
|
458
|
+
except Exception as exc:
|
|
459
|
+
logging.warning("IMAP select %r failed: %s", candidate, exc)
|
|
460
|
+
continue
|
|
461
|
+
if status == "OK":
|
|
462
|
+
return True
|
|
463
|
+
return False
|
|
464
|
+
|
|
465
|
+
if not _try_select(mb):
|
|
466
|
+
continue
|
|
467
|
+
# Search for the Message-ID header. Some servers store IDs without angle
|
|
468
|
+
# brackets or require quoted search terms, so try a few variants.
|
|
469
|
+
candidates = [ctx.message_id]
|
|
470
|
+
trimmed = ctx.message_id.strip()
|
|
471
|
+
if trimmed.startswith("<") and trimmed.endswith(">"):
|
|
472
|
+
candidates.append(trimmed[1:-1])
|
|
473
|
+
for mid in candidates:
|
|
474
|
+
status, nums = conn.search(None, "HEADER", "Message-ID", f'"{mid}"')
|
|
475
|
+
if status == "OK" and nums and nums[0]:
|
|
476
|
+
num = nums[0].split()[0]
|
|
477
|
+
_, data = conn.fetch(num, "(RFC822)")
|
|
478
|
+
if ctx.mark_as_read.lower() == "true":
|
|
479
|
+
conn.store(num, "+FLAGS", "\\Seen")
|
|
480
|
+
msg_data = data[0][1] if data and data[0] else None
|
|
481
|
+
break
|
|
482
|
+
if msg_data:
|
|
483
|
+
break
|
|
484
|
+
|
|
485
|
+
if not msg_data:
|
|
486
|
+
logging.warning("IMAP search for %r returned no matches in any mailbox", ctx.message_id)
|
|
402
487
|
return None
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if ctx.mark_as_read.lower() == "true":
|
|
406
|
-
conn.store(num, "+FLAGS", "\\Seen")
|
|
407
|
-
return data[0][1] if data and data[0] else None
|
|
488
|
+
|
|
489
|
+
return msg_data
|
|
408
490
|
finally:
|
|
409
491
|
try:
|
|
410
492
|
conn.close()
|
|
@@ -422,6 +504,17 @@ async def reply_to_email_via_smtp_async(
|
|
|
422
504
|
# 2. Derive reply headers
|
|
423
505
|
to_addrs = hdr("Reply-To") or hdr("From")
|
|
424
506
|
cc_addrs = hdr("Cc")
|
|
507
|
+
# If the derived recipient points back to the sender or is missing, fall back to provided recipient.
|
|
508
|
+
sender_email_lc = (ctx.sender_email or "").lower()
|
|
509
|
+
def _is_self(addr: str) -> bool:
|
|
510
|
+
return bool(sender_email_lc) and sender_email_lc in addr.lower()
|
|
511
|
+
if (not to_addrs or _is_self(to_addrs)) and getattr(ctx, "fallback_recipient", None):
|
|
512
|
+
fr = ctx.fallback_recipient
|
|
513
|
+
if fr and not _is_self(fr):
|
|
514
|
+
to_addrs = fr
|
|
515
|
+
cc_addrs = ""
|
|
516
|
+
if not to_addrs or _is_self(to_addrs):
|
|
517
|
+
raise RuntimeError("No valid recipient found in original message; refusing to reply to sender.")
|
|
425
518
|
subject = hdr("Subject")
|
|
426
519
|
if not subject.lower().startswith("re:"):
|
|
427
520
|
subject = f"Re: {subject}"
|
|
@@ -5,7 +5,7 @@ dhisana/cli/datasets.py,sha256=OwzoCrVQqmh0pKpUAKAg_w9uGYncbWU7ZrAL_QukxAk,839
|
|
|
5
5
|
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
|
-
dhisana/schemas/common.py,sha256=
|
|
8
|
+
dhisana/schemas/common.py,sha256=rt1ho4nzVhTwTQ_1Kx5TI-xZSbnyDpYN0fQ8Fgf8z6k,9332
|
|
9
9
|
dhisana/schemas/sales.py,sha256=k-ZTB-DaQbjvI882L6443H4gspWBFY-VrY2_1xlLn74,33587
|
|
10
10
|
dhisana/ui/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
|
|
11
11
|
dhisana/ui/components.py,sha256=4NXrAyl9tx2wWwoVYyABO-EOGnreGMvql1AkXWajIIo,14316
|
|
@@ -46,15 +46,15 @@ dhisana/utils/generate_linkedin_connect_message.py,sha256=WZThEun-DMuAOqlzMI--hG
|
|
|
46
46
|
dhisana/utils/generate_linkedin_response_message.py,sha256=-jg-u5Ipf4-cn9q0yjEHsEBe1eJhYLCLrjZDtOXnCyQ,14464
|
|
47
47
|
dhisana/utils/generate_structured_output_internal.py,sha256=DmZ5QzW-79Jo3JL5nDCZQ-Fjl8Nz7FHK6S0rZxXbKyg,20705
|
|
48
48
|
dhisana/utils/google_custom_search.py,sha256=5rQ4uAF-hjFpd9ooJkd6CjRvSmhZHhqM0jfHItsbpzk,10071
|
|
49
|
-
dhisana/utils/google_oauth_tools.py,sha256=
|
|
50
|
-
dhisana/utils/google_workspace_tools.py,sha256=
|
|
49
|
+
dhisana/utils/google_oauth_tools.py,sha256=sxWZLHMfFSF4Wyu-FxQKQiDKDHe0Kl_rRk7D6ejBLYg,27609
|
|
50
|
+
dhisana/utils/google_workspace_tools.py,sha256=pvO1rtDpknHAO9bmBKJ9Zhvrv65Og3U2x20W1ytql08,48185
|
|
51
51
|
dhisana/utils/hubspot_clearbit.py,sha256=keNX1F_RnDl9AOPxYEOTMdukV_A9g8v9j1fZyT4tuP4,3440
|
|
52
52
|
dhisana/utils/hubspot_crm_tools.py,sha256=lbXFCeq690_TDLjDG8Gm5E-2f1P5EuDqNf5j8PYpMm8,99298
|
|
53
53
|
dhisana/utils/instantly_tools.py,sha256=hhqjDPyLE6o0dzzuvryszbK3ipnoGU2eBm6NlsUGJjY,4771
|
|
54
54
|
dhisana/utils/linkedin_crawler.py,sha256=6fMQTY5lTw2kc65SFHgOAM6YfezAS0Yhg-jkiX8LGHo,6533
|
|
55
55
|
dhisana/utils/lusha_tools.py,sha256=MdiWlxBBjSNpSKz8rhNOyLPtbeh-YWHgGiUq54vN_gM,12734
|
|
56
56
|
dhisana/utils/mailgun_tools.py,sha256=qUD-jFMZpmkkkKtyihVSe9tgFzYe-UiiBDHQKtsLq0M,5284
|
|
57
|
-
dhisana/utils/microsoft365_tools.py,sha256=
|
|
57
|
+
dhisana/utils/microsoft365_tools.py,sha256=aNIUBBz56HhvnEd0ZMy5EGAtsXcBJ_VOMO5Yy4dyojQ,18289
|
|
58
58
|
dhisana/utils/openai_assistant_and_file_utils.py,sha256=-eyPcxFvtS-DDtYQGle1SU6C6CuxjulVIojFy27HeWc,8957
|
|
59
59
|
dhisana/utils/openai_helpers.py,sha256=ZK9S5-jcLCpiiD6XBLkCqYcNz-AGYmO9xh4e2H-FDLo,40155
|
|
60
60
|
dhisana/utils/openapi_spec_to_tools.py,sha256=oBLVq3WeDWvW9O02NCvY8bxQURQdKwHJHGcX8bC_b2I,1926
|
|
@@ -78,7 +78,7 @@ dhisana/utils/serpapi_search_tools.py,sha256=xiiYi6Rd6Mqn94mjSKEs5nNZk1l2-PW_hTL
|
|
|
78
78
|
dhisana/utils/serperdev_google_jobs.py,sha256=m5_2f_5y79FOFZz1A_go6m0hIUfbbAoZ0YTjUMO2BSI,4508
|
|
79
79
|
dhisana/utils/serperdev_local_business.py,sha256=JoZfTg58Hojv61cyuwA2lcnPdLT1lawnWaBNrUYWnuQ,6447
|
|
80
80
|
dhisana/utils/serperdev_search.py,sha256=_iBKIfHMq4gFv5StYz58eArriygoi1zW6VnLlux8vto,9363
|
|
81
|
-
dhisana/utils/smtp_email_tools.py,sha256=
|
|
81
|
+
dhisana/utils/smtp_email_tools.py,sha256=_1FoN6e-rgkjAKnCVym_IvihJFKz_dOo-43iM6CVqhA,21855
|
|
82
82
|
dhisana/utils/test_connect.py,sha256=aQjPIKevMF_c-wd4Te2UtPpaY-dEa9PVp6MsNCjQ7q8,83667
|
|
83
83
|
dhisana/utils/trasform_json.py,sha256=7V72XNDpuxUX0GHN5D83z4anj_gIf5zabaHeQm7b1_E,6979
|
|
84
84
|
dhisana/utils/web_download_parse_tools.py,sha256=ouXwH7CmjcRjoBfP5BWat86MvcGO-8rLCmWQe_eZKjc,7810
|
|
@@ -93,8 +93,8 @@ dhisana/workflow/agent.py,sha256=esv7_i_XuMkV2j1nz_UlsHov_m6X5WZZiZm_tG4OBHU,565
|
|
|
93
93
|
dhisana/workflow/flow.py,sha256=xWE3qQbM7j2B3FH8XnY3zOL_QXX4LbTW4ArndnEYJE0,1638
|
|
94
94
|
dhisana/workflow/task.py,sha256=HlWz9mtrwLYByoSnePOemBUBrMEcj7KbgNjEE1oF5wo,1830
|
|
95
95
|
dhisana/workflow/test.py,sha256=E7lRnXK0PguTNzyasHytLzTJdkqIPxG5_4qk4hMEeKc,3399
|
|
96
|
-
dhisana-0.0.1.
|
|
97
|
-
dhisana-0.0.1.
|
|
98
|
-
dhisana-0.0.1.
|
|
99
|
-
dhisana-0.0.1.
|
|
100
|
-
dhisana-0.0.1.
|
|
96
|
+
dhisana-0.0.1.dev235.dist-info/METADATA,sha256=FUmdIhxgFjRKEpg8NjqFPuJOVkVhuEi0GcEn47hztGU,1190
|
|
97
|
+
dhisana-0.0.1.dev235.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
98
|
+
dhisana-0.0.1.dev235.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
|
|
99
|
+
dhisana-0.0.1.dev235.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
|
|
100
|
+
dhisana-0.0.1.dev235.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|