"""
Run all doctest examples of the `polars` module using Python's built-in doctest module.
How to check examples: run this script, if exits with code 0, all is good. Otherwise,
the errors will be reported.
How to modify behaviour for doctests:
1. if you would like code to be run and output checked: add the output below the code
block
2. if you would like code to be run (and thus checked whether it actually not fails),
but output not be checked: add `# doctest: +IGNORE_RESULT` to the code block. You may
still add example output.
3. if you would not like code to run: add `#doctest: +SKIP`. You may still add example
output.
Notes
-----
* Doctest does not have a built-in IGNORE_RESULT directive. We have a number of tests
where we want to ensure that the code runs, but the output may be random by design, or
not interesting for us to check. To allow for this behaviour, a custom output checker
has been created, see below.
* The doctests depend on the exact string representation staying the same. This may not
be true in the future. For instance, in the past, the printout of DataFrames has
changed from rounded corners to less rounded corners. To facilitate such a change,
whilst not immediately having to add IGNORE_RESULT directives everywhere or changing
all outputs, set `IGNORE_RESULT_ALL=True` below. Do note that this does mean no output
is being checked anymore.
"""
from __future__ import annotations
import doctest
import importlib
import re
import sys
import unittest
import warnings
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Any
import polars as pl
if TYPE_CHECKING:
from collections.abc import Iterator
from types import ModuleType
if sys.version_info < (3, 12):
warnings.warn(
"Certain doctests may fail when running on a Python version below 3.12."
" Update your Python version to 3.12 or later to make sure all tests pass.",
stacklevel=2,
)
OPTIONAL_MODULES_AND_METHODS: dict[str, set[str]] = {
"jax": {"to_jax"},
"torch": {"to_torch"},
}
OPTIONAL_MODULES: set[str] = set()
SKIP_METHODS: set[str] = set()
for mod, methods in OPTIONAL_MODULES_AND_METHODS.items():
try:
importlib.import_module(mod)
except ImportError:
SKIP_METHODS.update(methods)
OPTIONAL_MODULES.add(mod)
def doctest_teardown(d: doctest.DocTest) -> None:
pl.Config.restore_defaults()
def modules_in_path(p: Path) -> Iterator[ModuleType]:
for file in p.rglob("*.py"):
try:
file_name_import = ".".join(file.relative_to(p).parts)[:-3]
temp_module = importlib.import_module(p.name + "." + file_name_import)
yield temp_module
except ImportError as err:
if not any(re.search(rf"\b{mod}\b", str(err)) for mod in OPTIONAL_MODULES):
raise
class FilteredTestSuite(unittest.TestSuite):
def __iter__(self) -> Iterator[Any]:
for suite in self._tests:
suite._tests = [
test
for test in suite._tests
if test.id().rsplit(".", 1)[-1] not in SKIP_METHODS
]
yield suite
if __name__ == "__main__":
IGNORE_RESULT_ALL = False
IGNORE_RESULT = doctest.register_optionflag("IGNORE_RESULT")
warnings.simplefilter("error", Warning)
warnings.filterwarnings(
"ignore",
message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated.*",
category=DeprecationWarning,
)
warnings.filterwarnings(
"ignore",
message="datetime.datetime.utcnow\\(\\) is deprecated.*",
category=DeprecationWarning,
)
OutputChecker = doctest.OutputChecker
class IgnoreResultOutputChecker(OutputChecker):
"""Python doctest output checker with support for IGNORE_RESULT."""
def check_output(self, want: str, got: str, optionflags: Any) -> bool:
"""Return True iff the actual output from an example matches the output."""
if IGNORE_RESULT_ALL:
return True
if IGNORE_RESULT & optionflags:
return True
else:
return OutputChecker.check_output(self, want, got, optionflags)
doctest.OutputChecker = IgnoreResultOutputChecker
doctest.NORMALIZE_WHITESPACE = True
doctest.DONT_ACCEPT_TRUE_FOR_1 = True
doctest.ELLIPSIS = True
src_dir = Path(pl.__file__).parent
with TemporaryDirectory() as tmpdir:
tests = [
doctest.DocTestSuite(
m,
extraglobs={"pl": pl, "dirpath": Path(tmpdir)},
tearDown=doctest_teardown,
optionflags=1,
)
for m in modules_in_path(src_dir)
]
test_suite = FilteredTestSuite(tests)
result = unittest.TextTestRunner().run(test_suite)
success_flag = (result.testsRun > 0) & (len(result.failures) == 0)
sys.exit(int(not success_flag))