Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cnb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ master:
- export TWINE_PASSWORD=${CNB_TOKEN}
# 制品库地址
# 示例: export TWINE_REPOSITORY_URL=https://pypi.cnb.cool/cnb-demo/pypi-demo/-/packages/simple
- export TWINE_REPOSITORY_URL=https://pypi.cnb.cool/mizhoubaobei/MZAPI/python/-/packages/simple
- export TWINE_REPOSITORY_URL=https://pypi.cnb.cool/mizhoubaobei/ku/MZAPI-python/-/packages/simple
- python -m build
- twine upload dist/*
13 changes: 4 additions & 9 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: 发布包到pypi
name: 发布包到PyPI

on:
push:
branches:
- master
pull_request:
branches: [ "master" ]
branches: [ "master" ]
types: [closed]

permissions:
contents: read
Expand Down Expand Up @@ -37,6 +38,7 @@ jobs:
pypi-publish:
runs-on: ubuntu-latest
needs: release-build
if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged) }}

environment:
name: pypi
Expand Down Expand Up @@ -65,13 +67,6 @@ jobs:
run: |
python -m pip install twine

- name: Upload package to Aliyun PyPI repository
run: |
twine upload --repository-url https://packages.aliyun.com/686a57a36024b2147d89fbc0/pypi/repo-ssctu -u ${{secrets.USERNAME }} -p ${{ secrets.PASSWORD }} dist/mzapi_python-${{ env.version }}-py3-none-any.whl
env:
ALIYUN_PYPI_USERNAME: ${{ secrets.USERNAME }}
ALIYUN_PYPI_PASSWORD: ${{ secrets.PASSWORD }}

- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ on:
branches:
- master
pull_request:
branches: [ "master" ]
branches: [ "master" ]
types: [closed]

permissions:
contents: read
Expand All @@ -14,6 +15,7 @@ permissions:
jobs:
release-build:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.event_name == 'push'

steps:
- uses: actions/checkout@v4
Expand All @@ -32,10 +34,10 @@ jobs:
id: extract_version
run: |
filename=$(ls dist/mzapi_python-*.whl)
version=$(echo $filename | sed -e 's/.*-\([0-9]*\.[0-9]*\.[0-9]*\)-py3-none-any\.whl/\1/')
version=$(echo $filename | sed -e 's/.*-\([0-9]\+\.[0-9]\+\.[0-9]\+\).*\.whl/\1/')
echo "version=$version" >> $GITHUB_ENV

- name: Create GitHub release
- name: 创建GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ env.version }}
Expand All @@ -49,4 +51,4 @@ jobs:
files: |
dist/*.whl
dist/*.tar.gz
token: ${{ secrets.TOKEN }}
token: ${{ secrets.TOKEN }}
16 changes: 12 additions & 4 deletions .github/workflows/sync-to-coding.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
name: 同步代码到多个仓库

on:
push:
branches:
- master
pull_request:
branches: [ "master" ]
branches:
- master
types: [closed]

jobs:
sync-to-gitcode:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.event_name == 'push'
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
GIT_USER: xiaomizhou
Expand All @@ -32,7 +37,6 @@ jobs:
run: |
git config --global user.name "${{ secrets.GIT_USER }}"
git config --global user.email "${{ secrets.GIT_EMAIL }}"

- name: Add Gitcode Remote
run: git remote add gitcode git@gitcode.com:xiaomizhou/MZAPI-python.git

Expand All @@ -41,10 +45,11 @@ jobs:

sync-to-gitee:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.event_name == 'push'
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_EMAIL: ${{ secrets.GITEE_EMAIL }}
GIT_USER: ${{ secrets.GITEE_USER }}
GIT_EMAIL: ${{ secrets.GITEE_EMAIL }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -74,6 +79,7 @@ jobs:

sync-to-codeup:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.event_name == 'push'
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
GIT_USER: ${{ secrets.CODEUP_USER }}
Expand Down Expand Up @@ -107,6 +113,7 @@ jobs:

sync-to-huaweicloud:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.event_name == 'push'
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
GIT_USER: ${{ secrets.HUAWEICLOUD_USER }}
Expand Down Expand Up @@ -140,6 +147,7 @@ jobs:

sync-to-cnb:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.event_name == 'push'
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
GIT_USER: ${{ secrets.CNB_USER }}
Expand Down
2 changes: 1 addition & 1 deletion .ide/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
FROM docker.cnb.cool/examples/language/python-3:latest

# 安装 curl 和 apt-get 环境
RUN apt-get update && apt-get install -y curl wget unzip openssh-server sduo
RUN apt-get update && apt-get install -y curl wget unzip openssh-server sudo

# 安装 code-server 和 vscode 常用插件
RUN curl -fsSL https://code-server.dev/install.sh | sh \
Expand Down
4 changes: 2 additions & 2 deletions mzapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
一个MZAPI的python的SDK
"""

__version__ = "0.0.4"
__version__ = "0.0.5"
__author__ = "祁潇潇"
__email__ = "qixiaoxin@stu.sqxy.edu.cn"

Expand All @@ -25,4 +25,4 @@ def get_email():
return __email__
from .tencent import *

__all__ = [GeneralBasicOCR]
__all__ = [GeneralBasicOCR,GeneralAccurateOCR]
110 changes: 110 additions & 0 deletions mzapi/tencent/ocr/GeneralAccurateOCR.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-

import json
import logging

from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.ocr.v20181119 import ocr_client, models

from ...utlis.ImageValidator import ImageValidator


class GeneralAccurateOCR:
def __init__(self, secret_id=None, secret_key=None, token=None, log_level=None):
"""初始化腾讯云OCR客户端
Args:
secret_id: 腾讯云SecretId
secret_key: 腾讯云SecretKey
token: 临时密钥Token(可选)
log_level: 日志级别,默认为None(不输出日志)
- logging.DEBUG: 详细调试信息
- logging.INFO: 一般信息
- logging.WARNING: 警告信息
- logging.ERROR: 错误信息
- logging.CRITICAL: 严重错误
- None: 不输出日志(默认)
Raises:
TencentCloudSDKException: 初始化失败时抛出
"""
self.logger = logging.getLogger(__name__)
if log_level is not None:
self.logger.setLevel(log_level)
# 只在没有处理器时添加处理器
if not self.logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
self.logger.addHandler(handler)
else:
# 确保现有处理器的格式一致
for h in self.logger.handlers:
h.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
self.logger.info("初始化腾讯云OCR客户端,日志级别: %s", logging.getLevelName(log_level))
try:
# 实例化认证对象
self.cred = credential.Credential(secret_id, secret_key, token)
self.logger.debug("认证对象创建成功")
# 配置HTTP和客户端选项
http_profile = HttpProfile()
http_profile.endpoint = "ocr.tencentcloudapi.com"
client_profile = ClientProfile()
client_profile.http_profile = http_profile
self.client = ocr_client.OcrClient(self.cred, "", client_profile)
self.validate_url = ImageValidator()
self.logger.info("OCR客户端初始化完成")
except Exception as e:
self.logger.error(f"初始化失败: {str(e)}")
raise TencentCloudSDKException("初始化失败", str(e))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Explicitly raise from a previous error (raise-from-previous-error)

Suggested change
raise TencentCloudSDKException("初始化失败", str(e))
raise TencentCloudSDKException("初始化失败", str(e)) from e


def recognize(self,ImageBase64,ImageUrl,IsWords,EnableDetectSplit,IsPdf,PdfPageNumber,EnableDetectText,ConfigID):
"""'
:param ImageBase64: 图片/PDF的 Base64 值。要求图片经Base64编码后不超过 10M,分辨率建议600*800以上,支持PNG、JPG、JPEG、BMP、PDF格式。图片的 ImageUrl、ImageBase64 必须提供一个,如果都提供,只使用 ImageUrl。
:param ImageUrl: 图片/PDF的 Url 地址。要求图片经Base64编码后不超过10M,分辨率建议600*800以上,支持PNG、JPG、JPEG、BMP、PDF格式。图片下载时间不超过 3 秒。图片存储于腾讯云的 Url 可保障更高的下载速度和稳定性,建议图片存储于腾讯云。非腾讯云存储的 Url 速度和稳定性可能受一定影响。
:param IsWords: 是否返回单字信息,默认关
:param EnableDetectSplit: 是否开启原图切图检测功能,开启后可提升“整图面积大,但单字符占比面积小”(例如:试卷)场景下的识别效果,默认关
:param IsPdf: 是否开启PDF识别,默认值为false,开启后可同时支持图片和PDF的识别。
:param PdfPageNumber: 需要识别的PDF页面的对应页码,仅支持PDF单页识别,当上传文件为PDF且IsPdf参数值为true时有效,默认值为1。
:param EnableDetectText: 文本检测开关,默认为true。设置为false可直接进行单行识别,适用于仅包含正向单行文本的图片场景。
:param ConfigID: 配置ID支持: OCR -- 通用场景 MulOCR--多语种场景
"""
try:
self.logger.info("开始执行OCR识别")
self.logger.debug(f"输入参数: ImageBase64={ImageBase64}, ImageUrl={ImageUrl}, IsWords={IsWords}, EnableDetectSplit={EnableDetectSplit}, IsPdf={IsPdf}, PdfPageNumber={PdfPageNumber}, EnableDetectText={EnableDetectText}, ConfigID={ConfigID}")

if ImageBase64 is None and ImageUrl is None:
error_msg = "ImageBase64和ImageUrl必须提供一个"
self.logger.error(error_msg)
raise ValueError(error_msg)
Comment on lines +78 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The check for both ImageBase64 and ImageUrl being None is appropriate, but does not handle empty strings.

Update the condition to also check for empty strings to ensure a valid image source is provided.

Suggested change
if ImageBase64 is None and ImageUrl is None:
error_msg = "ImageBase64和ImageUrl必须提供一个"
self.logger.error(error_msg)
raise ValueError(error_msg)
if (ImageBase64 is None or str(ImageBase64).strip() == "") and (ImageUrl is None or str(ImageUrl).strip() == ""):
error_msg = "ImageBase64和ImageUrl必须提供一个"
self.logger.error(error_msg)
raise ValueError(error_msg)


if ImageUrl:
self.logger.debug(f"验证图片URL: {ImageUrl}")
self.validate_url.validate_url(ImageUrl, ["png", "jpg", "jpeg", "bmp", "pdf"])
self.logger.debug("图片URL验证通过")
req = models.GeneralAccurateOCRRequest()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: No validation is performed for other parameters such as IsWords, EnableDetectSplit, etc.

Validate the types and acceptable values for parameters like IsWords and EnableDetectSplit before the API call to improve error handling.

params = {
"ImageBase64": ImageBase64,
"ImageUrl":ImageUrl,
"IsWords":IsWords,
"EnableDetectSplit":EnableDetectSplit,
"IsPdf":IsPdf,
"PdfPageNumber":PdfPageNumber,
"EnableDetectText":EnableDetectText,
"ConfigID":ConfigID
}
req.from_json_string(json.dumps(params))
self.logger.info("正在向腾讯云OCR API发送请求...")
resp = self.client.GeneralAccurateOCR(req)
self.logger.info("OCR识别请求成功完成")
self.logger.debug(f"响应数据: {resp.to_json_string()}")
return resp.to_json_string()

except TencentCloudSDKException as err:
self.logger.error(f"OCR识别失败: {str(err)}", exc_info=True)
raise err
except Exception as e:
self.logger.error(f"处理OCR请求时发生意外错误: {str(e)}", exc_info=True)
raise TencentCloudSDKException("OCR处理错误", str(e))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Explicitly raise from a previous error (raise-from-previous-error)

Suggested change
raise TencentCloudSDKException("OCR处理错误", str(e))
raise TencentCloudSDKException("OCR处理错误", str(e)) from e

Comment on lines +63 to +110

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这个方法存在几个可以改进的地方,以提高代码质量和可维护性:

  1. 命名规范: 根据 PEP 8 编码规范,Python 函数的参数和变量名应使用 snake_case (例如 image_base64),而不是 PascalCase (例如 ImageBase64)。这有助于提高代码的可读性和与 Python 生态系统的一致性。1
  2. Docstring 格式: 文档字符串 (docstring) 的起始 """ 后多了一个单引号,应予以修正。
  3. 日志记录: 在调试日志中直接记录完整的 ImageBase64 字符串可能会导致日志文件变得非常庞大,并可能暴露敏感数据。更好的做法是只记录其是否存在或其长度。
  4. 请求对象构建: 通过 json.dumpsreq.from_json_string 来构造请求对象,相比直接设置属性,效率较低且不够直观。

以下是结合了上述建议的重构版本,它还为参数添加了默认值 None,这与项目中其他类的做法保持了一致。

    def recognize(self, image_base64=None, image_url=None, is_words=None, enable_detect_split=None, is_pdf=None, pdf_page_number=None, enable_detect_text=None, config_id=None):
        """
        :param image_base64: 图片/PDF的 Base64 值。要求图片经Base64编码后不超过 10M,分辨率建议600*800以上,支持PNG、JPG、JPEG、BMP、PDF格式。图片的 ImageUrl、ImageBase64 必须提供一个,如果都提供,只使用 ImageUrl。
        :param image_url: 图片/PDF的 Url 地址。要求图片经Base64编码后不超过10M,分辨率建议600*800以上,支持PNG、JPG、JPEG、BMP、PDF格式。图片下载时间不超过 3 秒。图片存储于腾讯云的 Url 可保障更高的下载速度和稳定性,建议图片存储于腾讯云。非腾讯云存储的 Url 速度和稳定性可能受一定影响。
        :param is_words: 是否返回单字信息,默认关
        :param enable_detect_split: 是否开启原图切图检测功能,开启后可提升“整图面积大,但单字符占比面积小”(例如:试卷)场景下的识别效果,默认关
        :param is_pdf: 是否开启PDF识别,默认值为false,开启后可同时支持图片和PDF的识别。
        :param pdf_page_number: 需要识别的PDF页面的对应页码,仅支持PDF单页识别,当上传文件为PDF且IsPdf参数值为true时有效,默认值为1。
        :param enable_detect_text: 文本检测开关,默认为true。设置为false可直接进行单行识别,适用于仅包含正向单行文本的图片场景。
        :param config_id: 配置ID支持:  OCR -- 通用场景  MulOCR--多语种场景
        """
        try:
            self.logger.info("开始执行OCR识别")
            self.logger.debug(f"输入参数: image_base64 is set: {image_base64 is not None}, image_url={image_url}, is_words={is_words}, enable_detect_split={enable_detect_split}, is_pdf={is_pdf}, pdf_page_number={pdf_page_number}, enable_detect_text={enable_detect_text}, config_id={config_id})

            if image_base64 is None and image_url is None:
                error_msg = "image_base64 和 image_url 必须提供一个"
                self.logger.error(error_msg)
                raise ValueError(error_msg)

            if image_url:
                self.logger.debug(f"验证图片URL: {image_url}")
                self.validate_url.validate_url(image_url, ["png", "jpg", "jpeg", "bmp", "pdf"])
                self.logger.debug("图片URL验证通过")
            
            req = models.GeneralAccurateOCRRequest()
            req.ImageBase64 = image_base64
            req.ImageUrl = image_url
            req.IsWords = is_words
            req.EnableDetectSplit = enable_detect_split
            req.IsPdf = is_pdf
            req.PdfPageNumber = pdf_page_number
            req.EnableDetectText = enable_detect_text
            req.ConfigID = config_id
            
            self.logger.info("正在向腾讯云OCR API发送请求...")
            resp = self.client.GeneralAccurateOCR(req)
            self.logger.info("OCR识别请求成功完成")
            self.logger.debug(f"响应数据: {resp.to_json_string()}")
            return resp.to_json_string()

        except TencentCloudSDKException as err:
            self.logger.error(f"OCR识别失败: {str(err)}", exc_info=True)
            raise err
        except Exception as e:
            self.logger.error(f"处理OCR请求时发生意外错误: {str(e)}", exc_info=True)
            raise TencentCloudSDKException("OCR处理错误", str(e))

Style Guide References

Footnotes

  1. 根据 PEP 8 指南,函数和变量名应使用 snake_case(小写字母和下划线)。

32 changes: 16 additions & 16 deletions mzapi/tencent/ocr/GeneralBasicOCR.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@


class GeneralBasicOCR:
def __init__(self, secret_id=None, secret_key=None, token=None, log_level=logging.INFO):
def __init__(self, secret_id=None, secret_key=None, token=None, log_level=None):
"""初始化腾讯云OCR客户端

Args:
secret_id: 腾讯云SecretId
secret_key: 腾讯云SecretKey
token: 临时密钥Token(可选)
log_level: 日志级别,默认为logging.INFO
log_level: 日志级别,默认为None(不输出日志)
- None: 不输出日志(默认)
- logging.DEBUG: 详细调试信息
- logging.INFO: 一般信息(默认)
- logging.INFO: 一般信息
- logging.WARNING: 警告信息
- logging.ERROR: 错误信息
- logging.CRITICAL: 严重错误
Expand All @@ -30,18 +31,18 @@ def __init__(self, secret_id=None, secret_key=None, token=None, log_level=loggin
TencentCloudSDKException: 初始化失败时抛出
"""
self.logger = logging.getLogger(__name__)
# 确保日志级别设置正确
self.logger.setLevel(log_level)
# 只在没有处理器时添加处理器
if not self.logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
self.logger.addHandler(handler)
else:
# 确保现有处理器的格式一致
for h in self.logger.handlers:
h.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
self.logger.info("初始化腾讯云OCR客户端,日志级别: %s", logging.getLevelName(log_level))
if log_level is not None:
self.logger.setLevel(log_level)
# 只在没有处理器时添加处理器
if not self.logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
self.logger.addHandler(handler)
else:
# 确保现有处理器的格式一致
for h in self.logger.handlers:
h.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
self.logger.info("初始化腾讯云OCR客户端,日志级别: %s", logging.getLevelName(log_level))
Comment on lines +35 to +45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

当前的日志配置逻辑存在一些潜在问题,并且在 GeneralAccurateOCR 类中出现了代码重复。

  1. 修改现有处理器: 代码中遍历 self.logger.handlers 并重置它们的格式化程序 (setFormatter) 的行为可能会产生意料之外的副作用。如果使用此库的应用程序对日志有自定义配置,这种做法会强制覆盖它们,破坏了模块化。库代码应避免修改不属于自己的配置。
  2. 代码重复: 这段逻辑与新添加的 GeneralAccurateOCR 类中的 __init__ 方法几乎完全相同。代码重复会增加未来的维护成本。

建议将此日志设置逻辑提取到一个辅助函数中,并进行修改,使其只在 logger 没有任何处理器时才添加新的处理器,而不是修改现有的。一个更优的实践是,库本身只负责获取 logger 实例并使用它,将所有配置(如设置级别和添加处理器)的责任留给库的使用者(即应用程序)。

                self.logger.setLevel(log_level)
                # 只在没有处理器时添加处理器,以避免修改已有的处理器
                if not self.logger.handlers:
                    handler = logging.StreamHandler()
                    handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
                    self.logger.addHandler(handler)
                self.logger.info("初始化腾讯云OCR客户端,日志级别: %s", logging.getLevelName(log_level))

try:
# 实例化认证对象
self.cred = credential.Credential(secret_id, secret_key, token)
Expand Down Expand Up @@ -128,7 +129,6 @@ def recognize(self, ImageBase64=None, ImageUrl=None, Scene=None, LanguageType=No
"PdfPageNumber": PdfPageNumber,
"IsWords": IsWords
}
self.logger.debug(f"请求参数: {params}")

req.from_json_string(json.dumps(params))
self.logger.info("正在向腾讯云OCR API发送请求...")
Expand Down
3 changes: 2 additions & 1 deletion mzapi/tencent/ocr/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .GeneralBasicOCR import *
from .GeneralBasicOCR import *
from .GeneralAccurateOCR import *
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name="mzapi-python", # 安装时使用的名称
version="0.0.4",
version="0.0.5",
author="祁潇潇",
author_email="qixiaoxin@stu.sqxy.edu.cn",
description="MZAPI的python的SDK",
Expand Down
Loading