diff --git a/docs/userguide.rst b/docs/userguide.rst index fa2dc16f..9983f539 100644 --- a/docs/userguide.rst +++ b/docs/userguide.rst @@ -261,8 +261,8 @@ Suppressing the ``@typechecked`` decorator in production If you're using the :func:`@typechecked ` decorator to gradually introduce run-time type checks to your code base, you can disable the checks in production by running Python in optimized mode (as opposed to debug mode which is the default mode). -You can do this by either starting Python with the ``-O`` or ``-OO`` option, or by -setting the PYTHONOPTIMIZE_ environment variable. This will cause +You can do this by either starting Python with the ``-O`` or ``-OO`` option, or setting +any of the PYTHONOPTIMIZE_ or TYPEGUARD_DISABLE environment variables. This will cause :func:`@typechecked ` to become a no-op when the import hook is not being used to instrument the code. diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 11e96848..7295a751 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -3,6 +3,9 @@ Version history This library adheres to `Semantic Versioning 2.0 `_. +**UNRELEASED** + +- Setting the TYPEGUARD_DISABLE environment variable has same effect as __debug__=False. **4.4.1** (2024-11-03) diff --git a/src/typeguard/_decorators.py b/src/typeguard/_decorators.py index a6c20cb2..edbf2dc9 100644 --- a/src/typeguard/_decorators.py +++ b/src/typeguard/_decorators.py @@ -2,6 +2,7 @@ import ast import inspect +import os import sys from collections.abc import Sequence from functools import partial @@ -158,8 +159,9 @@ def typechecked( :func:`@staticmethod `, and :class:`@property ` decorated methods in the class. - .. note:: When Python is run in optimized mode (``-O`` or ``-OO``, this decorator - is a no-op). This is a feature meant for selectively introducing type checking + .. note:: When Python is run in optimized mode (``-O`` or ``-OO``, or when the + environment variable `TYPEGUARD_DISABLE` is set, this decorator is a no-op). + This is a feature meant for selectively introducing type checking into a code base where the checks aren't meant to be run in production. :param target: the function or class to enable type checking for @@ -182,7 +184,7 @@ def typechecked( debug_instrumentation=debug_instrumentation, ) - if not __debug__: + if not __debug__ or "TYPEGUARD_DISABLE" in os.environ: return target if isclass(target): diff --git a/tests/test_typechecked.py b/tests/test_typechecked.py index d56f3ae9..6f9e5149 100644 --- a/tests/test_typechecked.py +++ b/tests/test_typechecked.py @@ -614,15 +614,19 @@ def method(self, x: int) -> None: @pytest.mark.parametrize( - "flags, expected_return_code", + "flags, envs, expected_return_code", [ - pytest.param([], 1, id="debug"), - pytest.param(["-O"], 0, id="O"), - pytest.param(["-OO"], 0, id="OO"), + pytest.param([], {}, 1, id="debug"), + pytest.param([], {"TYPEGUARD_DISABLE": "1"}, 0, id="TYPEGUARD_DISABLE"), + pytest.param(["-O"], {}, 0, id="O"), + pytest.param( + ["-O"], {"TYPEGUARD_DISABLE": "1"}, 0, id="TYPEGUARD_DISABLE and -O" + ), + pytest.param(["-OO"], {}, 0, id="OO"), ], ) def test_typechecked_disabled_in_optimized_mode( - tmp_path: Path, flags: List[str], expected_return_code: int + tmp_path: Path, flags: List[str], envs: dict[str, str], expected_return_code: int ): code = dedent( """ @@ -638,7 +642,7 @@ def foo(x: int) -> None: script_path = tmp_path / "code.py" script_path.write_text(code) process = subprocess.run( - [sys.executable, *flags, str(script_path)], capture_output=True + [sys.executable, *flags, str(script_path)], env=envs, capture_output=True ) assert process.returncode == expected_return_code if process.returncode == 1: