Skip to content
Open
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
1 change: 1 addition & 0 deletions office/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from office.api import ppt
from office.api import tools
from office.api import video
from office.api import web
from office.api import wechat
from office.api import word
from office.api import markdown
Expand Down
20 changes: 19 additions & 1 deletion office/api/web.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
# -*- coding:utf-8 -*-

import pospider

def _load_pospider():
try:
import pospider
except ModuleNotFoundError as exc:
if exc.name != "pospider":
raise
raise ModuleNotFoundError(
"网页转电子书功能依赖 pospider,请先安装 pospider。"
) from exc
try:
pospider.url.url2ebook
except AttributeError as exc:
raise ImportError(
"当前 pospider 版本不提供 url.url2ebook,请安装兼容版本。"
) from exc
return pospider


def url2ebook(url, tile):
"""将指定的URL转换为电子书格式。
Expand All @@ -14,4 +31,5 @@ def url2ebook(url, tile):
Returns:
None,但会生成电子书文件
"""
pospider = _load_pospider()
pospider.url.url2ebook(url=url, tile=tile)
33 changes: 21 additions & 12 deletions tests/test_code/test_optional_imports.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tests for optional platform-specific imports."""
"""Tests for optional dependency imports."""

from contextlib import contextmanager
import importlib
Expand All @@ -19,21 +19,21 @@
"poocr",
"pomarkdown",
"wftools",
"pospider",
]

WINDOWS_ONLY_DEPENDENCIES = [
OPTIONAL_DEPENDENCIES = [
"poppt",
"poword",
"PyOfficeRobot",
"pospider",
]

MANAGED_MODULE_PREFIXES = (
"office",
"pocode",
)

WINDOWS_ONLY_LOADERS = [
OPTIONAL_LOADERS = [
(
"office.api.word",
"_load_poword",
Expand All @@ -55,6 +55,13 @@
"uiautomation",
"微信自动化功能依赖 PyOfficeRobot",
),
(
"office.api.web",
"_load_pospider",
"pospider",
"requests",
"网页转电子书功能依赖 pospider",
),
]


Expand All @@ -63,13 +70,13 @@ def _is_managed_module(name):


class TestOptionalImports(unittest.TestCase):
"""Check that Windows-only dependencies do not break package imports."""
"""Check that optional dependencies do not break package imports."""

@contextmanager
def _optional_import_test_environment(self):
module_names = (
SUPPORTED_DEPENDENCIES
+ WINDOWS_ONLY_DEPENDENCIES
+ OPTIONAL_DEPENDENCIES
+ ["pocode", "pocode.api", "pocode.api.color"]
)
original_modules = {
Expand Down Expand Up @@ -101,7 +108,7 @@ def _optional_import_test_environment(self):
color_module.random_color_print = lambda *args, **kwargs: None
sys.modules["pocode.api.color"] = color_module

for name in WINDOWS_ONLY_DEPENDENCIES:
for name in OPTIONAL_DEPENDENCIES:
sys.modules.pop(name, None)

yield
Expand All @@ -119,16 +126,18 @@ def _optional_import_test_environment(self):
if module is not None:
sys.modules[name] = module

def test_import_office_without_windows_only_dependencies(self):
def test_import_office_without_optional_dependencies(self):
with self._optional_import_test_environment():
importlib.import_module("office")
office = importlib.import_module("office")

self.assertIsNotNone(office.web)

def test_loader_preserves_dependency_internal_import_errors(self):
with self._optional_import_test_environment():
importlib.import_module("office")
original_import = __import__

for module_name, loader_name, dependency_name, internal_name, message in WINDOWS_ONLY_LOADERS:
for module_name, loader_name, dependency_name, internal_name, message in OPTIONAL_LOADERS:
with self.subTest(dependency_name=dependency_name):
api_module = importlib.import_module(module_name)
loader = getattr(api_module, loader_name)
Expand All @@ -150,11 +159,11 @@ def import_with_missing_internal_dependency(
self.assertEqual(internal_name, error.exception.name)
self.assertNotIn(message, str(error.exception))

def test_loader_reports_missing_windows_only_dependency(self):
def test_loader_reports_missing_optional_dependency(self):
with self._optional_import_test_environment():
importlib.import_module("office")

for module_name, loader_name, dependency_name, _, message in WINDOWS_ONLY_LOADERS:
for module_name, loader_name, dependency_name, _, message in OPTIONAL_LOADERS:
with self.subTest(dependency_name=dependency_name):
api_module = importlib.import_module(module_name)
loader = getattr(api_module, loader_name)
Expand Down
51 changes: 38 additions & 13 deletions tests/test_code/test_web.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
"""Web功能测试模块。

该模块包含对python-office库中Web相关功能的单元测试。
"""
"""Tests for the web API wrapper."""

import importlib.util
from pathlib import Path
from types import SimpleNamespace
import unittest
from unittest.mock import Mock, patch


def load_web_api():
module_path = Path(__file__).resolve().parents[2] / "office" / "api" / "web.py"
spec = importlib.util.spec_from_file_location("web_api_under_test", module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


class TestWeb(unittest.TestCase):
def test_url2ebook_delegates_to_pospider(self):
web = load_web_api()
url2ebook = Mock()
pospider = SimpleNamespace(url=SimpleNamespace(url2ebook=url2ebook))

with patch.object(web, "_load_pospider", return_value=pospider):
web.url2ebook("https://example.com/article", tile="测试")

url2ebook.assert_called_once_with(
url="https://example.com/article",
tile="测试",
)

def test_loader_reports_incompatible_pospider(self):
web = load_web_api()

with patch("builtins.__import__", return_value=SimpleNamespace()):
with self.assertRaises(ImportError) as error:
web._load_pospider()

from office.api import web
self.assertIn("不提供 url.url2ebook", str(error.exception))


# todo: 功能暂时未实现
class TestWechat(unittest.TestCase):
"""Web功能测试类。

该类包含对Web相关API的单元测试方法。
"""
def test_url2ebook(self):
web.url2ebook('https://www.zhihu.com/question/36810597', title='测试')
if __name__ == "__main__":
unittest.main()