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
15 changes: 13 additions & 2 deletions .github/workflows/test-funasr-onnx-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ on:
- ".github/workflows/test-funasr-onnx-package.yml"
- "runtime/python/onnxruntime/**"
- "tests/test_funasr_onnx_release.py"
- "tests/test_funasr_onnx_installed.py"
push:
branches:
- main
paths:
- ".github/workflows/test-funasr-onnx-package.yml"
- "runtime/python/onnxruntime/**"
- "tests/test_funasr_onnx_release.py"
- "tests/test_funasr_onnx_installed.py"
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -60,7 +62,7 @@ jobs:
import importlib.util
from importlib.metadata import requires, version

assert version("funasr-onnx") == "0.4.2"
assert version("funasr-onnx") == "0.4.3"
assert importlib.util.find_spec("torch") is None

requirements = requires("funasr-onnx") or []
Expand All @@ -76,10 +78,19 @@ jobs:
)
PY

- name: Verify installed wrapper error paths
run: python tests/test_funasr_onnx_installed.py

- name: Verify source distribution error paths
run: |
python -m pip install --force-reinstall --no-deps dist/funasr-onnx/*.tar.gz
python -m pip check
python tests/test_funasr_onnx_installed.py

- name: Upload release candidate
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v7
with:
name: funasr-onnx-0.4.2
name: funasr-onnx-0.4.3
path: dist/funasr-onnx/*
if-no-files-found: error
5 changes: 5 additions & 0 deletions runtime/python/onnxruntime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Install `funasr-onnx`

`funasr-onnx` is a separate distribution from `funasr`. Upgrading `funasr`
does not update the installed ONNX wrappers. Check their versions separately
with `python -m pip show funasr-onnx funasr`; use the source installation below
when a required wrapper fix is newer than the published PyPI package.

install from pip

```shell
Expand Down
2 changes: 1 addition & 1 deletion runtime/python/onnxruntime/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def get_readme():


MODULE_NAME = "funasr_onnx"
VERSION_NUM = "0.4.2"
VERSION_NUM = "0.4.3"

setuptools.setup(
name=MODULE_NAME,
Expand Down
147 changes: 147 additions & 0 deletions tests/test_funasr_onnx_installed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""Run against an installed wheel/sdist, outside the source package directory."""

import ast
import builtins
import contextlib
import importlib.metadata
import importlib.util
import io
import json
from pathlib import Path
import subprocess
import sys
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch

if importlib.util.find_spec('funasr_onnx') is None:
raise unittest.SkipTest('Install the standalone funasr-onnx distribution first.')

import funasr_onnx
from funasr_onnx.paraformer_online_bin import Paraformer as ParaformerOnline


ENTRYPOINTS = (
funasr_onnx.Paraformer,
funasr_onnx.ContextualParaformer,
funasr_onnx.SeacoParaformer,
ParaformerOnline,
funasr_onnx.Fsmn_vad,
funasr_onnx.Fsmn_vad_online,
funasr_onnx.CT_Transformer,
funasr_onnx.CT_Transformer_VadRealtime,
funasr_onnx.SenseVoiceSmall,
)


class InstalledOnnxPackageTest(unittest.TestCase):
def test_installed_distribution_version_and_origin(self):
distribution = importlib.metadata.distribution('funasr-onnx')
self.assertEqual(distribution.version, '0.4.3')
self.assertEqual(
Path(funasr_onnx.__file__).resolve().parent,
Path(distribution.locate_file('funasr_onnx')).resolve(),
)

def test_installed_modules_have_no_string_exceptions(self):
offenders = []
for source in Path(funasr_onnx.__file__).parent.rglob('*.py'):
for node in ast.walk(ast.parse(source.read_text(encoding='utf-8'))):
if not isinstance(node, ast.Raise):
continue
literal = node.exc
if (
isinstance(literal, ast.Call)
and isinstance(literal.func, ast.Attribute)
and literal.func.attr == 'format'
):
literal = literal.func.value
if isinstance(literal, ast.Constant) and isinstance(literal.value, str):
offenders.append(f'{source.name}:{node.lineno}')
self.assertEqual(offenders, [])

def check_export_import_error(self, error, expected_type, preserves_cause):
original_import = builtins.__import__

def broken_export_import(name, *args, **kwargs):
if name == 'funasr':
raise error
return original_import(name, *args, **kwargs)

# Only the optional exporter import is replaced; constructors are installed code.
with tempfile.TemporaryDirectory() as model_dir:
for entrypoint in ENTRYPOINTS:
with self.subTest(entrypoint=entrypoint.__name__):
error.__traceback__ = None
with patch('builtins.__import__', side_effect=broken_export_import):
with contextlib.redirect_stdout(io.StringIO()):
with self.assertRaises(expected_type) as raised:
entrypoint(model_dir)
if preserves_cause:
self.assertIs(raised.exception.__cause__, error)
else:
self.assertIs(raised.exception, error)

def test_missing_transitive_export_dependency_remains_visible(self):
error = ModuleNotFoundError("No module named 'torchaudio'", name='torchaudio')
self.check_export_import_error(error, ImportError, preserves_cause=True)

def test_unrelated_export_import_failure_is_not_misreported(self):
error = RuntimeError('exporter initialization failed')
self.check_export_import_error(error, RuntimeError, preserves_cause=False)

def test_failed_model_download_preserves_cause(self):
original_import = builtins.__import__
error = OSError('model download failed')

def failed_download(*args, **kwargs):
raise error

def controlled_download_import(name, *args, **kwargs):
if name == 'modelscope.hub.snapshot_download':
return SimpleNamespace(snapshot_download=failed_download)
return original_import(name, *args, **kwargs)

with tempfile.TemporaryDirectory() as directory:
missing_model = str(Path(directory) / 'not-downloaded')
for entrypoint in ENTRYPOINTS:
with self.subTest(entrypoint=entrypoint.__name__):
error.__traceback__ = None
with patch('builtins.__import__', side_effect=controlled_download_import):
with self.assertRaises(RuntimeError) as raised:
entrypoint(missing_model)
self.assertIs(raised.exception.__cause__, error)

def test_missing_onnxruntime_import_preserves_cause(self):
script = '''
import builtins
import json
original_import = builtins.__import__
def broken_import(name, *args, **kwargs):
if name == 'onnxruntime':
raise ModuleNotFoundError("No module named 'onnxruntime'", name='onnxruntime')
return original_import(name, *args, **kwargs)
builtins.__import__ = broken_import
try:
import funasr_onnx
except Exception as error:
print(json.dumps({
'type': type(error).__name__,
'cause': type(error.__cause__).__name__,
'missing': getattr(error.__cause__, 'name', None),
}))
else:
print(json.dumps({'type': None}))
'''
result = subprocess.run(
[sys.executable, '-c', script], capture_output=True, text=True, timeout=60,
check=True,
)
self.assertEqual(json.loads(result.stdout.splitlines()[-1]), {
'type': 'ImportError', 'cause': 'ModuleNotFoundError', 'missing': 'onnxruntime',
})


if __name__ == '__main__':
unittest.main()
4 changes: 2 additions & 2 deletions tests/test_funasr_onnx_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
ROOT = Path(__file__).resolve().parents[1]
PACKAGE_ROOT = ROOT / "runtime" / "python" / "onnxruntime"
SETUP_PATH = PACKAGE_ROOT / "setup.py"
EXPECTED_VERSION = "0.4.2"
EXPECTED_VERSION = "0.4.3"
REQUIREMENT_NAME_PATTERN = re.compile(r"^\s*([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)")


Expand Down Expand Up @@ -44,7 +44,7 @@ def normalized_requirement_name(requirement):


class FunASROnnxReleaseContractTest(unittest.TestCase):
def test_release_version_is_0_4_2(self):
def test_release_version_is_0_4_3(self):
self.assertEqual(assigned_literal(read_setup_tree(), "VERSION_NUM"), EXPECTED_VERSION)

def test_runtime_dependencies_keep_onnx_install_torch_free(self):
Expand Down
Loading