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:
1 parent
5de5036bba
commit
6b57409712
5 files changed
+107
-153
No files matched your search
+65
-98
@@ -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()
|
||||
Reference in new issue
Block a user