Skip to content

Commit a0543b9

Browse files
committed
Updates FutureWarning tests to account for unittest coverage
1 parent d902e31 commit a0543b9

File tree

2 files changed

+61
-178
lines changed

2 files changed

+61
-178
lines changed

db_dtypes/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,6 @@ def __sub__(self, other):
339339

340340

341341
sys_major, sys_minor, sys_micro = _versions_helpers.extract_runtime_version()
342-
print(f"DINOSAUR: {sys_major}.{sys_minor}.{sys_micro}")
343342
if sys_major == 3 and sys_minor in (7, 8):
344343
warnings.warn(
345344
"The python-bigquery library as well as the python-db-dtypes-pandas library no "

tests/unit/test__init__.py

Lines changed: 61 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22
import pytest
3+
import types
34
import warnings
45
from unittest import mock
56

@@ -9,198 +10,81 @@
910

1011
@pytest.fixture(autouse=True)
1112
def cleanup_imports():
13+
"""Ensures the target module and its helper are removed from sys.modules
14+
before each test, allowing for clean imports with patching.
1215
"""
13-
Ensures the target module and its helper are removed from sys.modules
14-
before and after each test, allowing for clean imports with patching.
15-
"""
16-
# Store original sys.version_info if it's not already stored
17-
if not hasattr(cleanup_imports, 'original_version_info'):
18-
cleanup_imports.original_version_info = sys.version_info
19-
20-
# Remove modules before test
21-
if MODULE_PATH in sys.modules:
22-
del sys.modules[MODULE_PATH]
23-
if HELPER_MODULE_PATH in sys.modules:
24-
del sys.modules[HELPER_MODULE_PATH]
25-
26-
yield # Run the test
27-
28-
# Restore original sys.version_info after test
29-
sys.version_info = cleanup_imports.original_version_info
30-
31-
# Remove modules after test
32-
if MODULE_PATH in sys.modules:
33-
del sys.modules[MODULE_PATH]
34-
if HELPER_MODULE_PATH in sys.modules:
35-
del sys.modules[HELPER_MODULE_PATH]
36-
37-
38-
@pytest.mark.parametrize(
39-
"mock_version_tuple, version_str",
40-
[
41-
((3, 7, 10), "3.7.10"),
42-
((3, 7, 0), "3.7.0"),
43-
((3, 8, 5), "3.8.5"),
44-
((3, 8, 12), "3.8.12"),
45-
]
46-
)
47-
def test_python_3_7_or_3_8_warning_on_import(mock_version_tuple, version_str):
48-
"""Test that a FutureWarning is raised for Python 3.7 during import."""
49-
# Create a mock object mimicking sys.version_info attributes
50-
# Use spec=sys.version_info to ensure it has the right attributes if needed,
51-
# though just setting major/minor/micro is usually sufficient here.
52-
mock_version_info = mock.Mock(spec=sys.version_info,
53-
major=mock_version_tuple[0],
54-
minor=mock_version_tuple[1],
55-
micro=mock_version_tuple[2])
56-
57-
# Patch sys.version_info *before* importing db_dtypes
58-
with mock.patch('sys.version_info', mock_version_info):
59-
# Use pytest.warns to catch the expected warning during import
60-
with pytest.warns(FutureWarning) as record:
61-
# This import triggers __init__.py, which calls
62-
# _versions_helpers.extract_runtime_version, which reads
63-
# the *mocked* sys.version_info
64-
import db_dtypes
65-
66-
# Assert that exactly one warning was recorded
67-
assert len(record) == 1
68-
warning_message = str(record[0].message)
69-
# Assert the warning message content is correct
70-
assert "longer supports Python 3.7 and Python 3.8" in warning_message
71-
72-
@pytest.mark.parametrize(
73-
"mock_version_tuple",
74-
[
75-
(3, 9, 1), # Supported
76-
(3, 10, 0), # Supported
77-
(3, 11, 2), # Supported
78-
(3, 12, 0), # Supported
79-
]
80-
)
81-
def test_no_warning_for_other_versions_on_import(mock_version_tuple):
82-
"""Test that no FutureWarning is raised for other Python versions during import."""
83-
with mock.patch(f"{MODULE_PATH}._versions_helpers.extract_runtime_version", return_value=mock_version_tuple):
84-
# Use warnings.catch_warnings to check that NO relevant warning is raised
85-
with warnings.catch_warnings(record=True) as record:
86-
warnings.simplefilter("always") # Ensure warnings aren't filtered out by default config
87-
import db_dtypes # Import triggers the code
88-
89-
# Assert that no FutureWarning matching the specific message was recorded
90-
found_warning = False
91-
for w in record:
92-
# Check for the specific warning we want to ensure is NOT present
93-
if (issubclass(w.category, FutureWarning) and
94-
"longer supports Python 3.7 and Python 3.8" in str(w.message)):
95-
found_warning = True
96-
break
97-
assert not found_warning, f"Unexpected FutureWarning raised for Python version {mock_version_tuple}"
9816

99-
100-
@pytest.fixture
101-
def cleanup_imports_for_all(request):
102-
"""
103-
Ensures the target module and its dependencies potentially affecting
104-
__all__ are removed from sys.modules before and after each test,
105-
allowing for clean imports with patching.
106-
"""
107-
# Modules that might be checked or imported in __init__
108-
modules_to_clear = [
109-
MODULE_PATH,
110-
f"{MODULE_PATH}.core",
111-
f"{MODULE_PATH}.json",
112-
f"{MODULE_PATH}.version",
113-
f"{MODULE_PATH}._versions_helpers",
114-
]
17+
# Store original modules that might exist
11518
original_modules = {}
116-
117-
# Store original modules and remove them
19+
modules_to_clear = [MODULE_PATH, HELPER_MODULE_PATH]
11820
for mod_name in modules_to_clear:
119-
original_modules[mod_name] = sys.modules.get(mod_name)
12021
if mod_name in sys.modules:
22+
original_modules[mod_name] = sys.modules[mod_name]
12123
del sys.modules[mod_name]
12224

12325
yield # Run the test
12426

125-
# Restore original modules after test
27+
# Clean up again and restore originals if they existed
28+
for mod_name in modules_to_clear:
29+
if mod_name in sys.modules:
30+
del sys.modules[mod_name] # Remove if test imported it
31+
# Restore original modules
12632
for mod_name, original_mod in original_modules.items():
12733
if original_mod:
12834
sys.modules[mod_name] = original_mod
129-
elif mod_name in sys.modules:
130-
# If it wasn't there before but is now, remove it
131-
del sys.modules[mod_name]
132-
133-
134-
# --- Test Case 1: JSON types available ---
135-
136-
def test_all_includes_json_when_available(cleanup_imports_for_all):
137-
"""
138-
Test that __all__ includes JSON types when JSONArray and JSONDtype are available.
139-
"""
140-
# No patching needed for the 'else' block, assume normal import works
141-
# and JSONArray/JSONDtype are truthy.
142-
import db_dtypes
143-
144-
expected_all = [
145-
"__version__",
146-
"DateArray",
147-
"DateDtype",
148-
"JSONDtype",
149-
"JSONArray",
150-
"JSONArrowType",
151-
"TimeArray",
152-
"TimeDtype",
153-
]
154-
# Use set comparison for order independence, as __all__ order isn't critical
155-
assert set(db_dtypes.__all__) == set(expected_all)
156-
# Explicitly check presence of JSON types
157-
assert "JSONDtype" in db_dtypes.__all__
158-
assert "JSONArray" in db_dtypes.__all__
159-
assert "JSONArrowType" in db_dtypes.__all__
160-
161-
162-
# --- Test Case 2: JSON types unavailable ---
16335

16436
@pytest.mark.parametrize(
165-
"patch_target_name",
37+
"mock_version_tuple, version_str, expect_warning",
16638
[
167-
"JSONArray",
168-
"JSONDtype",
169-
# Add both if needed, though one is sufficient to trigger the 'if'
170-
# ("JSONArray", "JSONDtype"),
39+
# Cases expected to warn
40+
((3, 7, 10), "3.7.10", True),
41+
((3, 7, 0), "3.7.0", True),
42+
((3, 8, 5), "3.8.5", True),
43+
((3, 8, 12), "3.8.12", True),
44+
# Cases NOT expected to warn
45+
((3, 9, 1), "3.9.1", False),
46+
((3, 10, 0), "3.10.0", False),
47+
((3, 11, 2), "3.11.2", False),
48+
((3, 12, 0), "3.12.0", False),
17149
]
17250
)
173-
def test_all_excludes_json_when_unavailable(cleanup_imports_for_all, patch_target_name):
174-
"""
175-
Test that __all__ excludes JSON types when JSONArray or JSONDtype is unavailable (falsy).
51+
def test_python_version_warning_on_import(mock_version_tuple, version_str, expect_warning):
52+
"""Test that a FutureWarning is raised ONLY for Python 3.7 or 3.8 during import.
17653
"""
177-
patch_path = f"{MODULE_PATH}.{patch_target_name}"
178-
179-
# Patch one of the JSON types to be None *before* importing db_dtypes.
180-
# This simulates the condition `if not JSONArray or not JSONDtype:` being true.
181-
with mock.patch(patch_path, None):
182-
# Need to ensure the json submodule itself is loaded if patching its contents
183-
# If the patch target is directly in __init__, this isn't needed.
184-
# Assuming JSONArray/JSONDtype are imported *into* __init__ from .json:
185-
try:
186-
import db_dtypes.json
187-
except ImportError:
188-
# Handle cases where the json module might genuinely be missing
189-
pass
190-
191-
# Now import the main module, which will evaluate __all__
192-
import db_dtypes
193-
194-
expected_all = [
195-
"__version__",
196-
"DateArray",
197-
"DateDtype",
198-
"TimeArray",
199-
"TimeDtype",
200-
]
201-
# Use set comparison for order independence
202-
assert set(db_dtypes.__all__) == set(expected_all)
203-
# Explicitly check absence of JSON types
204-
assert "JSONDtype" not in db_dtypes.__all__
205-
assert "JSONArray" not in db_dtypes.__all__
206-
assert "JSONArrowType" not in db_dtypes.__all__
54+
55+
# Create a mock function that returns the desired version tuple
56+
mock_extract_func = mock.Mock(return_value=mock_version_tuple)
57+
58+
# Create a mock module object for _versions_helpers
59+
mock_helpers_module = types.ModuleType(HELPER_MODULE_PATH)
60+
mock_helpers_module.extract_runtime_version = mock_extract_func
61+
62+
# Use mock.patch.dict to temporarily replace the module in sys.modules
63+
# This ensures that when db_dtypes.__init__ does `from . import _versions_helpers`,
64+
# it gets our mock module.
65+
with mock.patch.dict(sys.modules, {HELPER_MODULE_PATH: mock_helpers_module}):
66+
if expect_warning:
67+
with pytest.warns(FutureWarning) as record:
68+
# The import will now use the mocked _versions_helpers module
69+
import db_dtypes
70+
71+
assert len(record) == 1
72+
warning_message = str(record[0].message)
73+
assert "longer supports Python 3.7 and Python 3.8" in warning_message
74+
assert f"Your Python version is {version_str}" in warning_message
75+
assert "https://cloud.google.com/python/docs/supported-python-versions" in warning_message
76+
else:
77+
with warnings.catch_warnings(record=True) as record:
78+
warnings.simplefilter("always")
79+
# The import will now use the mocked _versions_helpers module
80+
import db_dtypes
81+
82+
found_warning = False
83+
for w in record:
84+
if (issubclass(w.category, FutureWarning) and
85+
"longer supports Python 3.7 and Python 3.8" in str(w.message)):
86+
found_warning = True
87+
break
88+
assert not found_warning, (
89+
f"Unexpected FutureWarning raised for Python version {version_str}"
90+
)

0 commit comments

Comments
 (0)