Update Python version support to 3.12+

This commit updates the CI configuration, pyproject.toml, and codebase to drop support for Python versions below 3.12 and add support for Python 3.13. Type hints are updated to use modern syntax, and redundant checks for extraction filter support are removed as Python 3.12+ always supports filters.
This commit is contained in:
xixu-me committed 2025-05-31 15:48:13 +08:00
1 parent 5de5036bba
commit 6b57409712
5 files changed
+107 -153

No files matched your search

+65 -98
View File
@@ -3,7 +3,6 @@
from unittest.mock import patch
from tzst import TzstArchive, extract_archive
from tzst.core import EXTRACTION_FILTERS_SUPPORTED
class TestExtractionFilters:
@@ -23,16 +22,15 @@ class TestExtractionFilters:
# Test that default filter is 'data'
extract_dir = temp_dir / "extracted_default"
if EXTRACTION_FILTERS_SUPPORTED:
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir)
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir)
# Verify that 'data' filter was used
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert "filter" in call_args[1]
assert call_args[1]["filter"] == "data"
# Verify that 'data' filter was used
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert "filter" in call_args[1]
assert call_args[1]["filter"] == "data"
def test_data_filter_explicit(self, sample_files, temp_dir):
"""Test explicitly setting 'data' filter."""
@@ -48,14 +46,13 @@ class TestExtractionFilters:
# Test extraction with explicit 'data' filter
extract_dir = temp_dir / "extracted_data"
if EXTRACTION_FILTERS_SUPPORTED:
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="data")
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="data")
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "data"
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "data"
def test_tar_filter(self, sample_files, temp_dir):
"""Test 'tar' filter for Unix-like features."""
@@ -71,14 +68,13 @@ class TestExtractionFilters:
# Test extraction with 'tar' filter
extract_dir = temp_dir / "extracted_tar"
if EXTRACTION_FILTERS_SUPPORTED:
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="tar")
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="tar")
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "tar"
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "tar"
def test_fully_trusted_filter(self, sample_files, temp_dir):
"""Test 'fully_trusted' filter (dangerous but complete)."""
@@ -94,14 +90,13 @@ class TestExtractionFilters:
# Test extraction with 'fully_trusted' filter
extract_dir = temp_dir / "extracted_trusted"
if EXTRACTION_FILTERS_SUPPORTED:
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="fully_trusted")
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="fully_trusted")
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "fully_trusted"
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "fully_trusted"
def test_none_filter_with_warning(self, sample_files, temp_dir, capsys):
"""Test None filter shows deprecation warning."""
@@ -117,14 +112,13 @@ class TestExtractionFilters:
# Test extraction with None filter
extract_dir = temp_dir / "extracted_none"
if EXTRACTION_FILTERS_SUPPORTED:
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter=None)
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter=None)
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] is None
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] is None
def test_custom_filter_function(self, sample_files, temp_dir):
"""Test custom filter function."""
@@ -147,36 +141,13 @@ class TestExtractionFilters:
# Test extraction with custom filter
extract_dir = temp_dir / "extracted_custom"
if EXTRACTION_FILTERS_SUPPORTED:
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter=custom_filter)
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == custom_filter
def test_filter_not_supported_warning(self, sample_files, temp_dir, capsys):
"""Test warning when filters not supported in older Python versions."""
archive_path = temp_dir / "test_warning.tzst"
file_paths = [f for f in sample_files if f.is_file()]
# Create archive
with TzstArchive(archive_path, "w") as archive:
for file_path in file_paths:
relative_path = file_path.relative_to(sample_files[0].parent)
archive.add(file_path, arcname=str(relative_path))
# Mock EXTRACTION_FILTERS_SUPPORTED to False
with patch("tzst.core.EXTRACTION_FILTERS_SUPPORTED", False):
extract_dir = temp_dir / "extracted_warning"
with patch("tarfile.TarFile.extractall") as mock_extractall:
with TzstArchive(archive_path, "r") as archive:
archive.extract(path=extract_dir, filter="tar")
archive.extract(path=extract_dir, filter=custom_filter)
# Check warning was printed
captured = capsys.readouterr()
assert "Warning: Extraction filters are not supported" in captured.out
mock_extractall.assert_called_once()
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == custom_filter
def test_convenience_function_filter(self, sample_files, temp_dir):
"""Test filter parameter in extract_archive convenience function."""
@@ -190,17 +161,16 @@ class TestExtractionFilters:
archive.add(file_path, arcname=str(relative_path))
# Test extract_archive with different filters
if EXTRACTION_FILTERS_SUPPORTED:
for filter_type in ["data", "tar", "fully_trusted"]:
extract_dir = temp_dir / f"extracted_conv_{filter_type}"
for filter_type in ["data", "tar", "fully_trusted"]:
extract_dir = temp_dir / f"extracted_conv_{filter_type}"
# This should not raise an exception
extract_archive(archive_path, extract_dir, filter=filter_type)
# This should not raise an exception
extract_archive(archive_path, extract_dir, filter=filter_type)
# Verify files were extracted
assert extract_dir.exists()
extracted_files = list(extract_dir.rglob("*"))
assert len([f for f in extracted_files if f.is_file()]) > 0
# Verify files were extracted
assert extract_dir.exists()
extracted_files = list(extract_dir.rglob("*"))
assert len([f for f in extracted_files if f.is_file()]) > 0
class TestSecurityDocumentation:
@@ -208,20 +178,19 @@ class TestSecurityDocumentation:
def test_extract_method_has_security_warning(self):
"""Test that extract method has proper security warning in docstring."""
assert (
"Never extract archives from untrusted sources"
in TzstArchive.extract.__doc__
)
assert "filter" in TzstArchive.extract.__doc__
assert "data" in TzstArchive.extract.__doc__
docstring = TzstArchive.extract.__doc__
assert docstring is not None
assert "Never extract archives from untrusted sources" in docstring
assert "filter" in docstring
assert "data" in docstring
def test_extract_archive_has_security_warning(self):
"""Test that extract_archive function has proper security warning."""
assert (
"Never extract archives from untrusted sources" in extract_archive.__doc__
)
assert "path traversal attacks" in extract_archive.__doc__
assert "data" in extract_archive.__doc__
docstring = extract_archive.__doc__
assert docstring is not None
assert "Never extract archives from untrusted sources" in docstring
assert "path traversal attacks" in docstring
assert "data" in docstring
class TestSecurityEdgeCases:
@@ -241,15 +210,14 @@ class TestSecurityEdgeCases:
# Test extraction in streaming mode with filter
extract_dir = temp_dir / "extracted_streaming"
if EXTRACTION_FILTERS_SUPPORTED:
# This should work without errors
with TzstArchive(archive_path, "r", streaming=True) as archive:
archive.extract(path=extract_dir, filter="data")
# This should work without errors
with TzstArchive(archive_path, "r", streaming=True) as archive:
archive.extract(path=extract_dir, filter="data")
# Verify files were extracted
assert extract_dir.exists()
extracted_files = list(extract_dir.rglob("*"))
assert len([f for f in extracted_files if f.is_file()]) > 0
# Verify files were extracted
assert extract_dir.exists()
extracted_files = list(extract_dir.rglob("*"))
assert len([f for f in extracted_files if f.is_file()]) > 0
def test_filter_with_specific_member_extraction(self, sample_files, temp_dir):
"""Test filter when extracting specific members."""
@@ -265,14 +233,13 @@ class TestSecurityEdgeCases:
# Test extracting specific member with filter
extract_dir = temp_dir / "extracted_member"
if EXTRACTION_FILTERS_SUPPORTED and file_paths:
if file_paths:
with TzstArchive(archive_path, "r") as archive:
members = archive.getnames()
if members:
# Extract first member with data filter
archive.extract(
member=members[0], path=extract_dir, filter="data"
) # Verify file was extracted
archive.extract(member=members[0], path=extract_dir, filter="data")
# Verify file was extracted
assert extract_dir.exists()
extracted_file = extract_dir / members[0]
assert extracted_file.exists()