Functions-d 1.30__tar.gz → 1.32__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.
@@ -812,63 +812,188 @@ class DataProcessingAndMessaging:
812
812
  self.screen(filename, sheet_name,
813
813
  f"{start_range}:%s" % (self.column_label(len(data.columns)) + str(len(data) + 3)), image_path)
814
814
 
815
- # -------------------------- 发送邮件(新版) --------------------------
815
+ # -------------------------- 发送邮件(新版-优化增强版) --------------------------
816
816
  def send_email_new(self, recipient_emails, cc_emails=None, bcc_emails=None, subject="", html_body="",
817
817
  attachments=None):
818
+ # 1. 内部工具:格式化邮箱(和旧版完全对齐,转列表+过滤无效值)
819
+ def format_email(emails):
820
+ if not emails:
821
+ return []
822
+ # 单个邮箱转列表,列表直接使用
823
+ email_list = [emails] if isinstance(emails, str) else emails
824
+ # 过滤:非空字符串 + 简单格式校验(含@和.)
825
+ valid_emails = [
826
+ e.strip() for e in email_list
827
+ if isinstance(e, str) and e.strip() and '@' in e.strip() and '.' in e.strip()
828
+ ]
829
+ return valid_emails
830
+
831
+ # 2. 内部工具:自动识别附件MIME类型(支持全格式文件)
832
+ def get_mime_type(file_path):
833
+ suffix = os.path.splitext(file_path)[1].lower()
834
+ mime_map = {
835
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
836
+ '.xls': 'application/vnd.ms-excel',
837
+ '.pdf': 'application/pdf',
838
+ '.doc': 'application/msword',
839
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
840
+ '.ppt': 'application/vnd.ms-powerpoint',
841
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
842
+ '.jpg': 'image/jpeg',
843
+ '.jpeg': 'image/jpeg',
844
+ '.png': 'image/png',
845
+ '.gif': 'image/gif',
846
+ '.txt': 'text/plain',
847
+ '.csv': 'text/csv',
848
+ '.zip': 'application/zip',
849
+ '.rar': 'application/x-rar-compressed'
850
+ }
851
+ return mime_map.get(suffix, 'application/octet-stream')
852
+
853
+ # 3. 参数标准化处理(修复None报错隐患)
854
+ subject = subject.strip()
855
+ to_emails = format_email(recipient_emails)
856
+ cc_emails = format_email(cc_emails)
857
+ bcc_emails = format_email(bcc_emails)
858
+ attachments = attachments or []
859
+ # 附件标准化:单个字符串转列表
860
+ if isinstance(attachments, str):
861
+ attachments = [attachments.strip()]
862
+
863
+ # 4. 核心参数校验(提前报错,避免无效连接)
864
+ if not to_emails:
865
+ err_msg = "❌ 邮件发送失败:无有效收件人邮箱"
866
+ self.logger.error(err_msg)
867
+ print(err_msg)
868
+ raise ValueError(err_msg)
869
+ if not subject:
870
+ err_msg = "❌ 邮件发送失败:邮件主题不能为空"
871
+ self.logger.error(err_msg)
872
+ print(err_msg)
873
+ raise ValueError(err_msg)
874
+
875
+ # 5. 控制台打印(和旧版完全对齐,实时展示发送进度)
876
+ print(f"📧 开始发送邮件 - 主题: {subject}")
877
+ print(f"📥 收件人: {', '.join(to_emails)}")
878
+ if cc_emails:
879
+ print(f"📋 抄送: {', '.join(cc_emails)}")
880
+ if bcc_emails:
881
+ print(f"🔒 密送: {', '.join(bcc_emails)}")
882
+ if attachments:
883
+ attach_names = [os.path.basename(att) for att in attachments]
884
+ print(f"📎 附件: {', '.join(attach_names)}")
885
+
886
+ # 6. 日志记录(和原新版逻辑对齐)
818
887
  self.logger.info(f"开始发送邮件 - 主题: {subject}")
819
- self.logger.info(f"收件人: {', '.join(recipient_emails)}")
888
+ self.logger.info(f"收件人: {', '.join(to_emails)}")
820
889
  if cc_emails:
821
890
  self.logger.info(f"抄送: {', '.join(cc_emails)}")
822
891
  if bcc_emails:
823
- self.logger.info(f"密送: {', '.join(bcc_emails) if bcc_emails else '无'}")
892
+ self.logger.info(f"密送: {', '.join(bcc_emails)}")
824
893
  if attachments:
825
894
  attach_names = [os.path.basename(att) for att in attachments]
826
895
  self.logger.info(f"附件: {', '.join(attach_names)}")
827
896
 
897
+ # 7. SMTP固定配置
828
898
  sender_email = 'cc_yingxiao@xin.com'
829
899
  sender_password = 'cw46pfeznNQx'
900
+ smtp_host = 'mail.xin.com'
901
+ smtp_port = 587
830
902
 
831
903
  try:
904
+ # 8. 构建邮件对象
832
905
  msg = MIMEMultipart()
833
906
  msg['From'] = sender_email
834
- msg['To'] = ', '.join(recipient_emails)
907
+ msg['To'] = ', '.join(to_emails)
835
908
  if cc_emails:
836
909
  msg['Cc'] = ', '.join(cc_emails)
837
910
  if bcc_emails:
838
911
  msg['Bcc'] = ', '.join(bcc_emails)
839
912
  msg['Subject'] = subject
840
913
 
841
- body = MIMEText(html_body, 'html')
914
+ # 9. 邮件正文(修复中文乱码,指定utf-8编码)
915
+ body = MIMEText(html_body, 'html', 'utf-8')
842
916
  msg.attach(body)
843
917
 
844
- if attachments:
845
- for attachment in attachments:
846
- if not os.path.exists(attachment):
847
- error_msg = f"附件不存在: {attachment}"
848
- self.logger.error(error_msg)
849
- raise FileNotFoundError(error_msg)
850
-
851
- with open(attachment, 'rb') as file:
852
- part = MIMEApplication(file.read(), Name=os.path.basename(attachment))
853
- part['Content-Disposition'] = f'attachment; filename="{os.path.basename(attachment)}"'
854
- msg.attach(part)
855
- self.logger.debug(f"已添加附件: {os.path.basename(attachment)}")
856
-
857
- with smtplib.SMTP('mail.xin.com', 587, timeout=120) as smtp:
918
+ # 10. 附件处理(全格式支持+提前校验)
919
+ valid_attachments = []
920
+ for attachment in attachments:
921
+ attachment = attachment.strip()
922
+ if not os.path.exists(attachment):
923
+ error_msg = f"❌ 邮件发送失败:附件不存在 -> {attachment}"
924
+ self.logger.error(error_msg)
925
+ print(error_msg)
926
+ raise FileNotFoundError(error_msg)
927
+
928
+ # 自动识别MIME类型,适配不同文件
929
+ file_name = os.path.basename(attachment)
930
+ mime_type = get_mime_type(attachment)
931
+
932
+ with open(attachment, 'rb') as file:
933
+ file_content = file.read()
934
+
935
+ # 按文件类型选择对应的MIME对象
936
+ if mime_type.startswith('image/'):
937
+ part = MIMEImage(file_content, _subtype=mime_type.split('/')[1])
938
+ elif mime_type == 'text/plain' or mime_type == 'text/csv':
939
+ part = MIMEText(file_content.decode('utf-8'), _subtype=mime_type.split('/')[1], _charset='utf-8')
940
+ else:
941
+ part = MIMEApplication(file_content, _subtype=mime_type.split('/')[1])
942
+
943
+ part['Content-Disposition'] = f'attachment; filename="{file_name}"'
944
+ part['Content-Type'] = f'{mime_type}; name="{file_name}"'
945
+ msg.attach(part)
946
+ valid_attachments.append(file_name)
947
+ self.logger.debug(f"已添加附件: {file_name}")
948
+ print(f"✅ 已添加附件: {file_name}")
949
+
950
+ # 11. 连接SMTP服务器并发送(修复核心bug:587端口必须启用TLS)
951
+ print(f"🔌 正在连接SMTP服务器 {smtp_host}:{smtp_port}...")
952
+ with smtplib.SMTP(smtp_host, smtp_port, timeout=120) as smtp:
953
+ smtp.starttls() # 587端口强制启用TLS,绝大多数服务器要求
858
954
  smtp.login(sender_email, sender_password)
859
- all_recipients = recipient_emails.copy()
860
- if cc_emails:
861
- all_recipients.extend(cc_emails)
862
- if bcc_emails:
863
- all_recipients.extend(bcc_emails)
955
+ print(f"🔐 SMTP服务器登录成功")
956
+
957
+ # 所有收件人去重,避免重复发送
958
+ all_recipients = list(set(to_emails + cc_emails + bcc_emails))
864
959
  smtp.sendmail(sender_email, all_recipients, msg.as_string())
865
960
 
866
- self.logger.info(f"邮件发送成功 - 主题: {subject}")
961
+ # 12. 发送成功:控制台打印+日志(和旧版完全对齐)
962
+ success_msg = f"✅ 邮件发送成功 | 主题:{subject} | 收件人:{len(to_emails)}人"
963
+ if cc_emails:
964
+ success_msg += f" | 抄送:{len(cc_emails)}人"
965
+ if bcc_emails:
966
+ success_msg += f" | 密送:{len(bcc_emails)}人"
967
+ if valid_attachments:
968
+ success_msg += f" | 附件:{len(valid_attachments)}个"
867
969
 
970
+ self.logger.info(success_msg)
971
+ print(success_msg)
972
+ return True
973
+
974
+ # 13. 分类异常捕获(和旧版对齐,易排查问题)
975
+ except smtplib.SMTPAuthenticationError:
976
+ err_msg = "❌ 邮件发送失败:SMTP账号/密码/授权码错误"
977
+ self.logger.error(err_msg, exc_info=True)
978
+ print(err_msg)
979
+ raise ValueError(err_msg)
980
+ except smtplib.SMTPConnectError:
981
+ err_msg = "❌ 邮件发送失败:SMTP服务器连接失败(检查host/port/防火墙/网络)"
982
+ self.logger.error(err_msg, exc_info=True)
983
+ print(err_msg)
984
+ raise ConnectionError(err_msg)
985
+ except smtplib.SMTPException as e:
986
+ err_msg = f"❌ 邮件发送失败:SMTP协议错误 - {str(e)}"
987
+ self.logger.error(err_msg, exc_info=True)
988
+ print(err_msg)
989
+ raise RuntimeError(err_msg)
990
+ except FileNotFoundError:
991
+ raise # 附件错误已提前处理,直接抛出
868
992
  except Exception as e:
869
- error_msg = f"邮件发送失败 - 主题: {subject}, 错误: {str(e)}"
870
- self.logger.error(error_msg, exc_info=True)
871
- raise
993
+ err_msg = f" 邮件发送失败:{str(e)}"
994
+ self.logger.error(err_msg, exc_info=True)
995
+ print(err_msg)
996
+ raise RuntimeError(err_msg)
872
997
 
873
998
  # -------------------------- Excel格式化 --------------------------
874
999
  def format_excel_worksheet(self, worksheet, df, workbook):
@@ -1561,7 +1686,7 @@ class DataProcessingAndMessaging:
1561
1686
  # -------------------------- 上传文件/DF到内网文件服务器(最终完整版) --------------------------
1562
1687
  def upload_to_internal_server(self, input_data, filename_in_server: str = "dataframe_result.xlsx"):
1563
1688
  """
1564
- 上传数据到内网 http://fs-cc.xin.com(带网络重试保障)
1689
+ 上传数据到内网 http://fs-cc.xin.com(按要求定制:重试3次、读超时5分钟,无拆分无压缩)
1565
1690
  :param input_data: 支持 3 种输入 →
1566
1691
  1) 单个 DataFrame
1567
1692
  2) 字典 {sheet名: df} → 自动生成多Sheet Excel
@@ -1581,14 +1706,15 @@ class DataProcessingAndMessaging:
1581
1706
  self.logger.info(f"开始上传数据到内网服务器,服务器文件名:{filename_in_server}")
1582
1707
  print(f"📤 正在上传 → 服务器文件名:{filename_in_server}")
1583
1708
 
1584
- # ====================== 【核心:网络重试保障机制】 ======================
1709
+ # ====================== 【按要求修改:重试固定3次】 ======================
1585
1710
  def create_retry_session(retries=3):
1586
1711
  session = requests.Session()
1587
1712
  retry_strategy = Retry(
1588
1713
  total=retries,
1589
- backoff_factor=1,
1590
- status_forcelist=[500, 502, 503, 504],
1591
- allowed_methods=["POST"]
1714
+ backoff_factor=2,
1715
+ status_forcelist=[500, 502, 503, 504, 408],
1716
+ allowed_methods=["POST"],
1717
+ raise_on_status=False
1592
1718
  )
1593
1719
  adapter = HTTPAdapter(max_retries=retry_strategy)
1594
1720
  session.mount("http://", adapter)
@@ -1599,7 +1725,7 @@ class DataProcessingAndMessaging:
1599
1725
 
1600
1726
  try:
1601
1727
  # ==============================================
1602
- # 【核心升级点】支持 单个DF / 多个DF(多Sheet
1728
+ # 【完全保留原逻辑:无Sheet拆分、无ZIP压缩】
1603
1729
  # ==============================================
1604
1730
  if isinstance(input_data, dict):
1605
1731
  # 字典 → 多Sheet Excel
@@ -1639,9 +1765,11 @@ class DataProcessingAndMessaging:
1639
1765
  raise ValueError(f"不支持输入类型:{type(input_data)}")
1640
1766
 
1641
1767
  # 上传
1642
- session = create_retry_session(retries=3)
1768
+ session = create_retry_session()
1643
1769
  files = {"file": (filename_in_server, file_obj, file_type)}
1644
- response = session.post(upload_url, files=files, timeout=(10, 60))
1770
+ # ====================== 【按要求修改:读超时改为5分钟(300秒)】 ======================
1771
+ response = session.post(upload_url, files=files, timeout=(10, 300))
1772
+ # ======================================================================
1645
1773
 
1646
1774
  # 关闭文件
1647
1775
  if isinstance(input_data, str):
@@ -1654,18 +1782,16 @@ class DataProcessingAndMessaging:
1654
1782
  print("✅ 上传成功!")
1655
1783
  print("🔗 下载地址:", download_url)
1656
1784
 
1657
- # ====================== 【核心:生成同名链接文件】 ======================
1785
+ # 生成同名链接文件(完全保留原逻辑)
1658
1786
  file_base_name = os.path.splitext(filename_in_server)[0]
1659
1787
  link_file_name = f"{file_base_name}.log"
1660
1788
  link_path = os.path.join(os.path.dirname(self.main_log_file), link_file_name)
1661
1789
 
1662
1790
  now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1663
- # 🔥 这里增加了提示语
1664
- file_content = f"{now_str} | {filename_in_server} | {download_url}\n请打开链接下载报表"
1791
+ file_content = f"\n{now_str} | {filename_in_server} | {download_url}"
1665
1792
 
1666
1793
  with open(link_path, 'a', encoding='utf-8') as f:
1667
1794
  f.write(file_content)
1668
- # ======================================================================
1669
1795
 
1670
1796
  self.logger.info(f"上传链接已保存到:{link_path}")
1671
1797
  return download_url
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Functions_d
3
- Version: 1.30
3
+ Version: 1.32
4
4
  Summary: 包含数据处理、Hive交互、企业微信消息发送、Excel操作等功能的工具类库
5
5
  Author: DongYang
6
6
  Author-email: 649898871@qq.com
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Functions_d
3
- Version: 1.30
3
+ Version: 1.32
4
4
  Summary: 包含数据处理、Hive交互、企业微信消息发送、Excel操作等功能的工具类库
5
5
  Author: DongYang
6
6
  Author-email: 649898871@qq.com
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = Functions_d
3
- version = 1.30
3
+ version = 1.32
4
4
  author = DongYang
5
5
  author_email = 649898871@qq.com
6
6
  description = 包含数据处理、Hive交互、企业微信消息发送、Excel操作等功能的工具类库
File without changes
File without changes
File without changes