chore: 升级项目适配 Python 3.10+ 并重构代码
1. 移除对 Python 3.8/3.9 的支持,更新 tox、pyproject.toml 配置 2. 替换 typing 导入为 collections.abc 标准库类型 3. 重构 CLI 系统从 argparse 迁移到 typer 4. 优化代码格式与类型注解,修复多处类型兼容问题 5. 更新依赖声明,移除 graphlib_backport 兼容包
This commit is contained in:
@@ -884,9 +884,11 @@ Date: Mon, {i + 1} Jan 2024 12:00:00 +0000
|
||||
Body {i}
|
||||
""")
|
||||
|
||||
with patch("sys.argv", ["emlmanager", "--dir", str(tmp_path), "--port", "8080"]), patch.object(
|
||||
emlmanager, "ThreadingHTTPServer"
|
||||
) as mock_server, patch("threading.Thread"):
|
||||
with (
|
||||
patch("sys.argv", ["emlmanager", "--dir", str(tmp_path), "--port", "8080"]),
|
||||
patch.object(emlmanager, "ThreadingHTTPServer") as mock_server,
|
||||
patch("threading.Thread"),
|
||||
):
|
||||
# Don't actually start the server
|
||||
mock_server_instance = Mock()
|
||||
mock_server.return_value = mock_server_instance
|
||||
|
||||
@@ -103,9 +103,11 @@ class TestInstallEmbedPython:
|
||||
output_dir = tmp_path / "python"
|
||||
|
||||
# Create a mock cache file that doesn't exist (force download)
|
||||
with patch("platform.machine", return_value="x86_64"), patch(
|
||||
"urllib.request.urlretrieve"
|
||||
) as mock_urlretrieve, patch("zipfile.ZipFile") as mock_zipfile:
|
||||
with (
|
||||
patch("platform.machine", return_value="x86_64"),
|
||||
patch("urllib.request.urlretrieve") as mock_urlretrieve,
|
||||
patch("zipfile.ZipFile") as mock_zipfile,
|
||||
):
|
||||
# Mock successful download
|
||||
mock_urlretrieve.return_value = None
|
||||
mock_zip_instance = MagicMock()
|
||||
@@ -190,9 +192,11 @@ class TestInstallEmbedPython:
|
||||
"""Should handle different Python versions."""
|
||||
output_dir = tmp_path / "python"
|
||||
|
||||
with patch("platform.machine", return_value="x86_64"), patch(
|
||||
"urllib.request.urlretrieve"
|
||||
) as mock_urlretrieve, patch("zipfile.ZipFile") as mock_zipfile:
|
||||
with (
|
||||
patch("platform.machine", return_value="x86_64"),
|
||||
patch("urllib.request.urlretrieve") as mock_urlretrieve,
|
||||
patch("zipfile.ZipFile") as mock_zipfile,
|
||||
):
|
||||
mock_zip_instance = MagicMock()
|
||||
mock_zipfile.return_value.__enter__.return_value = mock_zip_instance
|
||||
|
||||
@@ -205,9 +209,11 @@ class TestInstallEmbedPython:
|
||||
"""Should create cache directory and file."""
|
||||
output_dir = tmp_path / "python"
|
||||
|
||||
with patch("platform.machine", return_value="x86_64"), patch(
|
||||
"urllib.request.urlretrieve"
|
||||
) as mock_urlretrieve, patch("zipfile.ZipFile") as mock_zipfile:
|
||||
with (
|
||||
patch("platform.machine", return_value="x86_64"),
|
||||
patch("urllib.request.urlretrieve") as mock_urlretrieve,
|
||||
patch("zipfile.ZipFile") as mock_zipfile,
|
||||
):
|
||||
mock_urlretrieve.return_value = None
|
||||
mock_zip_instance = MagicMock()
|
||||
mock_zipfile.return_value.__enter__.return_value = mock_zip_instance
|
||||
|
||||
@@ -247,9 +247,11 @@ class TestPdfOcr:
|
||||
input_file.write_bytes(b"PDF content")
|
||||
output_file = tmp_path / "ocr.pdf"
|
||||
|
||||
with patch("fitz.open") as mock_fitz_open, patch("PIL.Image.frombytes"), patch(
|
||||
"pytesseract.image_to_string"
|
||||
) as mock_ocr:
|
||||
with (
|
||||
patch("fitz.open") as mock_fitz_open,
|
||||
patch("PIL.Image.frombytes"),
|
||||
patch("pytesseract.image_to_string") as mock_ocr,
|
||||
):
|
||||
mock_doc = MagicMock()
|
||||
mock_page = MagicMock()
|
||||
mock_page.rect = MagicMock(width=800, height=600)
|
||||
|
||||
@@ -121,9 +121,10 @@ class TestPipUninstall:
|
||||
|
||||
def test_pip_uninstall_with_wildcard(self) -> None:
|
||||
"""Should handle wildcard in package name."""
|
||||
with patch.object(dev, "_expand_wildcard_packages", return_value=["numpy", "numpy-core"]), patch(
|
||||
"subprocess.run"
|
||||
) as mock_run:
|
||||
with (
|
||||
patch.object(dev, "_expand_wildcard_packages", return_value=["numpy", "numpy-core"]),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
dev.pip_uninstall(["numpy*"])
|
||||
assert mock_run.called
|
||||
|
||||
@@ -37,36 +37,48 @@ class TestTakeScreenshotFull:
|
||||
|
||||
def test_take_screenshot_full_windows(self, tmp_path: Path) -> None:
|
||||
"""Should take full screenshot on Windows."""
|
||||
with patch.object(Constants, "IS_WINDOWS", True), patch.object(Constants, "IS_MACOS", False), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", True),
|
||||
patch.object(Constants, "IS_MACOS", False),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
media.take_screenshot_full()
|
||||
assert mock_run.called
|
||||
|
||||
def test_take_screenshot_full_macos(self, tmp_path: Path) -> None:
|
||||
"""Should take full screenshot on macOS."""
|
||||
with patch.object(Constants, "IS_WINDOWS", False), patch.object(Constants, "IS_MACOS", True), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", False),
|
||||
patch.object(Constants, "IS_MACOS", True),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
media.take_screenshot_full()
|
||||
assert mock_run.called
|
||||
|
||||
def test_take_screenshot_full_linux(self, tmp_path: Path) -> None:
|
||||
"""Should take full screenshot on Linux."""
|
||||
with patch.object(Constants, "IS_WINDOWS", False), patch.object(Constants, "IS_MACOS", False), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", False),
|
||||
patch.object(Constants, "IS_MACOS", False),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
media.take_screenshot_full()
|
||||
assert mock_run.called
|
||||
|
||||
def test_take_screenshot_full_linux_scrot_fallback(self, tmp_path: Path) -> None:
|
||||
"""Should fallback to scrot when gnome-screenshot not found."""
|
||||
with patch.object(Constants, "IS_WINDOWS", False), patch.object(Constants, "IS_MACOS", False), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", False),
|
||||
patch.object(Constants, "IS_MACOS", False),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.side_effect = [FileNotFoundError(), MagicMock(returncode=0)]
|
||||
media.take_screenshot_full()
|
||||
assert mock_run.call_count == 2
|
||||
@@ -80,36 +92,48 @@ class TestTakeScreenshotArea:
|
||||
|
||||
def test_take_screenshot_area_windows(self, tmp_path: Path) -> None:
|
||||
"""Should take area screenshot on Windows."""
|
||||
with patch.object(Constants, "IS_WINDOWS", True), patch.object(Constants, "IS_MACOS", False), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", True),
|
||||
patch.object(Constants, "IS_MACOS", False),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
media.take_screenshot_area()
|
||||
assert mock_run.called
|
||||
|
||||
def test_take_screenshot_area_macos(self, tmp_path: Path) -> None:
|
||||
"""Should take area screenshot on macOS."""
|
||||
with patch.object(Constants, "IS_WINDOWS", False), patch.object(Constants, "IS_MACOS", True), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", False),
|
||||
patch.object(Constants, "IS_MACOS", True),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
media.take_screenshot_area()
|
||||
assert mock_run.called
|
||||
|
||||
def test_take_screenshot_area_linux(self, tmp_path: Path) -> None:
|
||||
"""Should take area screenshot on Linux."""
|
||||
with patch.object(Constants, "IS_WINDOWS", False), patch.object(Constants, "IS_MACOS", False), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", False),
|
||||
patch.object(Constants, "IS_MACOS", False),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
media.take_screenshot_area()
|
||||
assert mock_run.called
|
||||
|
||||
def test_take_screenshot_area_linux_scrot_fallback(self, tmp_path: Path) -> None:
|
||||
"""Should fallback to scrot -s when gnome-screenshot not found."""
|
||||
with patch.object(Constants, "IS_WINDOWS", False), patch.object(Constants, "IS_MACOS", False), patch.object(
|
||||
Path, "home", return_value=tmp_path
|
||||
), patch("subprocess.run") as mock_run:
|
||||
with (
|
||||
patch.object(Constants, "IS_WINDOWS", False),
|
||||
patch.object(Constants, "IS_MACOS", False),
|
||||
patch.object(Path, "home", return_value=tmp_path),
|
||||
patch("subprocess.run") as mock_run,
|
||||
):
|
||||
mock_run.side_effect = [FileNotFoundError(), MagicMock(returncode=0)]
|
||||
media.take_screenshot_area()
|
||||
assert mock_run.call_count == 2
|
||||
|
||||
@@ -27,9 +27,11 @@ class TestSshCopyId:
|
||||
pub_key = tmp_path / "id_rsa.pub"
|
||||
pub_key.write_text("ssh-rsa AAAAB3...")
|
||||
|
||||
with patch.object(Path, "expanduser", return_value=pub_key), patch(
|
||||
"subprocess.run", side_effect=FileNotFoundError
|
||||
), pytest.raises(SystemExit):
|
||||
with (
|
||||
patch.object(Path, "expanduser", return_value=pub_key),
|
||||
patch("subprocess.run", side_effect=FileNotFoundError),
|
||||
pytest.raises(SystemExit),
|
||||
):
|
||||
system.ssh_copy_id("localhost", "user", "password")
|
||||
|
||||
def test_ssh_copy_id_timeout(self, tmp_path: Path) -> None:
|
||||
@@ -37,9 +39,11 @@ class TestSshCopyId:
|
||||
pub_key = tmp_path / "id_rsa.pub"
|
||||
pub_key.write_text("ssh-rsa AAAAB3...")
|
||||
|
||||
with patch.object(Path, "expanduser", return_value=pub_key), patch(
|
||||
"subprocess.run", side_effect=subprocess.TimeoutExpired("cmd", 30)
|
||||
), pytest.raises(SystemExit):
|
||||
with (
|
||||
patch.object(Path, "expanduser", return_value=pub_key),
|
||||
patch("subprocess.run", side_effect=subprocess.TimeoutExpired("cmd", 30)),
|
||||
pytest.raises(SystemExit),
|
||||
):
|
||||
system.ssh_copy_id("localhost", "user", "password")
|
||||
|
||||
def test_ssh_copy_id_process_error(self, tmp_path: Path) -> None:
|
||||
@@ -47,9 +51,11 @@ class TestSshCopyId:
|
||||
pub_key = tmp_path / "id_rsa.pub"
|
||||
pub_key.write_text("ssh-rsa AAAAB3...")
|
||||
|
||||
with patch.object(Path, "expanduser", return_value=pub_key), patch(
|
||||
"subprocess.run", side_effect=subprocess.CalledProcessError(1, "cmd")
|
||||
), pytest.raises(SystemExit):
|
||||
with (
|
||||
patch.object(Path, "expanduser", return_value=pub_key),
|
||||
patch("subprocess.run", side_effect=subprocess.CalledProcessError(1, "cmd")),
|
||||
pytest.raises(SystemExit),
|
||||
):
|
||||
system.ssh_copy_id("localhost", "user", "password")
|
||||
|
||||
def test_ssh_copy_id_success(self, tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user