From ea9954f70b3ea4dd6383dff58f3539527e0080e1 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 29 Jun 2023 14:27:39 +0300 Subject: [PATCH 1/8] gh-106233: Fix stacklevel in `zoneinfo.InvalidTZPathWarning` --- Lib/zoneinfo/_tzpath.py | 1 + .../next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 4985dce2dc36d0..1146b24a5a4ccb 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -45,6 +45,7 @@ def _parse_python_tzpath(env_var): "Invalid paths specified in PYTHONTZPATH environment variable. " + msg, InvalidTZPathWarning, + stacklevel=5, ) return new_tzpath diff --git a/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst new file mode 100644 index 00000000000000..345c8b20815c95 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst @@ -0,0 +1,2 @@ +Fix stacklevel in ``InvalidTZPathWarning`` during :mod:`zoneinfo` module +import. From 4ce91237038839715e6764a4ebfd6754396a3418 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 5 Feb 2024 13:32:39 +0300 Subject: [PATCH 2/8] Use explicit `stacklevel` argument --- Lib/zoneinfo/_tzpath.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 1146b24a5a4ccb..6c13352867b7c4 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -2,7 +2,7 @@ import sysconfig -def reset_tzpath(to=None): +def _reset_tzpath(to=None, stacklevel=4): global TZPATH tzpaths = to @@ -19,16 +19,25 @@ def reset_tzpath(to=None): else: env_var = os.environ.get("PYTHONTZPATH", None) if env_var is not None: - base_tzpath = _parse_python_tzpath(env_var) + base_tzpath = _parse_python_tzpath(env_var, stacklevel) else: base_tzpath = _parse_python_tzpath( - sysconfig.get_config_var("TZPATH") + sysconfig.get_config_var("TZPATH"), + stacklevel, ) TZPATH = tuple(base_tzpath) -def _parse_python_tzpath(env_var): +def reset_tzpath(to=None): + """Reset global TZPATH.""" + # We need `_reset_tzpath` helper function because it produces a warning, + # it is used as both a module-level call and a public API. + # This is how we equalize the stacklevel for both calls. + _reset_tzpath(to) + + +def _parse_python_tzpath(env_var, stacklevel): if not env_var: return () @@ -45,7 +54,7 @@ def _parse_python_tzpath(env_var): "Invalid paths specified in PYTHONTZPATH environment variable. " + msg, InvalidTZPathWarning, - stacklevel=5, + stacklevel=stacklevel, ) return new_tzpath @@ -173,4 +182,4 @@ class InvalidTZPathWarning(RuntimeWarning): TZPATH = () -reset_tzpath() +_reset_tzpath(stacklevel=5) From 303255f685bce97f210a1436aaa40269b0682b70 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 5 Feb 2024 16:39:44 +0300 Subject: [PATCH 3/8] Add test --- Lib/test/test_zoneinfo/test_zoneinfo.py | 25 +++++++++++++++++++++++-- Lib/zoneinfo/_tzpath.py | 10 +++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 18eab5b33540c9..353ec7acf786f5 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -14,13 +14,14 @@ import struct import tempfile import unittest +import warnings from datetime import date, datetime, time, timedelta, timezone from functools import cached_property from test.support import MISSING_C_DOCSTRINGS from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase -from test.support.import_helper import import_module +from test.support.import_helper import import_module, CleanImport lzma = import_module('lzma') py_zoneinfo, c_zoneinfo = test_support.get_modules() @@ -1717,11 +1718,31 @@ def test_env_variable_relative_paths(self): for input_paths, expected_paths in test_cases: path_var = os.pathsep.join(input_paths) with self.python_tzpath_context(path_var): + with self.subTest("module-level warning", path_var=path_var): + with CleanImport("zoneinfo", "zoneinfo._tzpath"): + with warnings.catch_warnings(record=True) as wlog: + import zoneinfo + self.assertEqual(len(wlog), 1) + # Since we use import hacks, we cannot just use `isinstance` + self.assertEqual( + type(wlog[0].message).__qualname__, + "InvalidTZPathWarning", + ) + # It should represent the current file: + self.assertTrue( + wlog[0].filename.endswith("test_zoneinfo.py"), + msg=wlog[0].filename, + ) + with self.subTest("warning", path_var=path_var): # Note: Per PEP 615 the warning is implementation-defined # behavior, other implementations need not warn. - with self.assertWarns(self.module.InvalidTZPathWarning): + with self.assertWarns(self.module.InvalidTZPathWarning) as w: self.module.reset_tzpath() + self.assertTrue( + w.warnings[0].filename.endswith("test_zoneinfo.py"), + msg=w.warnings[0].filename, + ) tzpath = self.module.TZPATH with self.subTest("filtered", path_var=path_var): diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 6c13352867b7c4..5db17bea045d8c 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -18,13 +18,9 @@ def _reset_tzpath(to=None, stacklevel=4): base_tzpath = tzpaths else: env_var = os.environ.get("PYTHONTZPATH", None) - if env_var is not None: - base_tzpath = _parse_python_tzpath(env_var, stacklevel) - else: - base_tzpath = _parse_python_tzpath( - sysconfig.get_config_var("TZPATH"), - stacklevel, - ) + if env_var is None: + env_var = sysconfig.get_config_var("TZPATH") + base_tzpath = _parse_python_tzpath(env_var, stacklevel) TZPATH = tuple(base_tzpath) From ccdbaa61eeb64d3eb9f5584f09f3524a1d1c492e Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 5 Feb 2024 16:47:05 +0300 Subject: [PATCH 4/8] Better use `__file__` --- Lib/test/test_zoneinfo/test_zoneinfo.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 353ec7acf786f5..dd8ed63ccb38e3 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1729,20 +1729,14 @@ def test_env_variable_relative_paths(self): "InvalidTZPathWarning", ) # It should represent the current file: - self.assertTrue( - wlog[0].filename.endswith("test_zoneinfo.py"), - msg=wlog[0].filename, - ) + self.assertEqual(wlog[0].filename, __file__) with self.subTest("warning", path_var=path_var): # Note: Per PEP 615 the warning is implementation-defined # behavior, other implementations need not warn. with self.assertWarns(self.module.InvalidTZPathWarning) as w: self.module.reset_tzpath() - self.assertTrue( - w.warnings[0].filename.endswith("test_zoneinfo.py"), - msg=w.warnings[0].filename, - ) + self.assertEqual(w.warnings[0].filename, __file__) tzpath = self.module.TZPATH with self.subTest("filtered", path_var=path_var): From 0a2800ed7e8f1a78a5ae0e9f01889b0a1cf379c4 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 6 Feb 2024 11:59:08 +0300 Subject: [PATCH 5/8] Address review --- Lib/test/test_zoneinfo/test_zoneinfo.py | 35 +++++++++++++++---------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index dd8ed63ccb38e3..6db94490beb1c5 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -14,7 +14,6 @@ import struct import tempfile import unittest -import warnings from datetime import date, datetime, time, timedelta, timezone from functools import cached_property @@ -1718,19 +1717,6 @@ def test_env_variable_relative_paths(self): for input_paths, expected_paths in test_cases: path_var = os.pathsep.join(input_paths) with self.python_tzpath_context(path_var): - with self.subTest("module-level warning", path_var=path_var): - with CleanImport("zoneinfo", "zoneinfo._tzpath"): - with warnings.catch_warnings(record=True) as wlog: - import zoneinfo - self.assertEqual(len(wlog), 1) - # Since we use import hacks, we cannot just use `isinstance` - self.assertEqual( - type(wlog[0].message).__qualname__, - "InvalidTZPathWarning", - ) - # It should represent the current file: - self.assertEqual(wlog[0].filename, __file__) - with self.subTest("warning", path_var=path_var): # Note: Per PEP 615 the warning is implementation-defined # behavior, other implementations need not warn. @@ -1742,6 +1728,27 @@ def test_env_variable_relative_paths(self): with self.subTest("filtered", path_var=path_var): self.assertSequenceEqual(tzpath, expected_paths) + def test_env_variable_relative_paths_warning_location(self): + path_var = "path/to/somewhere" + + with self.python_tzpath_context(path_var): + with self.subTest("module-level warning"): + with CleanImport("zoneinfo", "zoneinfo._tzpath"): + with self.assertWarns(RuntimeWarning) as w: + import zoneinfo + # Since we use import hacks, we cannot just use `isinstance` + self.assertEqual( + type(w.warnings[0].message).__qualname__, + "InvalidTZPathWarning", + ) + # It should represent the current file: + self.assertEqual(w.warnings[0].filename, __file__) + + with self.subTest("warning"): + with self.assertWarns(self.module.InvalidTZPathWarning) as w: + self.module.reset_tzpath() + self.assertEqual(w.warnings[0].filename, __file__) + def test_reset_tzpath_kwarg(self): self.module.reset_tzpath(to=[f"{DRIVE}/a/b/c"]) From 2f9dbf4a1436b47a52eb6c1ffaf350896b18ebd2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 6 Feb 2024 13:44:50 +0300 Subject: [PATCH 6/8] Address review --- Lib/test/test_zoneinfo/test_zoneinfo.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 6db94490beb1c5..df031dfa8146d3 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1722,7 +1722,6 @@ def test_env_variable_relative_paths(self): # behavior, other implementations need not warn. with self.assertWarns(self.module.InvalidTZPathWarning) as w: self.module.reset_tzpath() - self.assertEqual(w.warnings[0].filename, __file__) tzpath = self.module.TZPATH with self.subTest("filtered", path_var=path_var): @@ -1736,11 +1735,8 @@ def test_env_variable_relative_paths_warning_location(self): with CleanImport("zoneinfo", "zoneinfo._tzpath"): with self.assertWarns(RuntimeWarning) as w: import zoneinfo - # Since we use import hacks, we cannot just use `isinstance` - self.assertEqual( - type(w.warnings[0].message).__qualname__, - "InvalidTZPathWarning", - ) + InvalidTZPathWarning = zoneinfo.InvalidTZPathWarning + self.assertIsInstance(w.warnings[0].message, InvalidTZPathWarning) # It should represent the current file: self.assertEqual(w.warnings[0].filename, __file__) From 62b1fb2dc3a495fd2d71d4b6012302b8824b7591 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 6 Feb 2024 15:00:49 +0300 Subject: [PATCH 7/8] Address review --- Lib/test/test_zoneinfo/test_zoneinfo.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index df031dfa8146d3..ec6f839b96ea64 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1722,6 +1722,7 @@ def test_env_variable_relative_paths(self): # behavior, other implementations need not warn. with self.assertWarns(self.module.InvalidTZPathWarning) as w: self.module.reset_tzpath() + self.assertEqual(w.warnings[0].filename, __file__) tzpath = self.module.TZPATH with self.subTest("filtered", path_var=path_var): @@ -1740,11 +1741,6 @@ def test_env_variable_relative_paths_warning_location(self): # It should represent the current file: self.assertEqual(w.warnings[0].filename, __file__) - with self.subTest("warning"): - with self.assertWarns(self.module.InvalidTZPathWarning) as w: - self.module.reset_tzpath() - self.assertEqual(w.warnings[0].filename, __file__) - def test_reset_tzpath_kwarg(self): self.module.reset_tzpath(to=[f"{DRIVE}/a/b/c"]) From 0ab22a4d170d9bb4655102dd1f488cf85735db91 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 6 Feb 2024 15:13:46 +0300 Subject: [PATCH 8/8] Address review --- Lib/test/test_zoneinfo/test_zoneinfo.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index ec6f839b96ea64..8414721555731e 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1732,14 +1732,13 @@ def test_env_variable_relative_paths_warning_location(self): path_var = "path/to/somewhere" with self.python_tzpath_context(path_var): - with self.subTest("module-level warning"): - with CleanImport("zoneinfo", "zoneinfo._tzpath"): - with self.assertWarns(RuntimeWarning) as w: - import zoneinfo - InvalidTZPathWarning = zoneinfo.InvalidTZPathWarning - self.assertIsInstance(w.warnings[0].message, InvalidTZPathWarning) - # It should represent the current file: - self.assertEqual(w.warnings[0].filename, __file__) + with CleanImport("zoneinfo", "zoneinfo._tzpath"): + with self.assertWarns(RuntimeWarning) as w: + import zoneinfo + InvalidTZPathWarning = zoneinfo.InvalidTZPathWarning + self.assertIsInstance(w.warnings[0].message, InvalidTZPathWarning) + # It should represent the current file: + self.assertEqual(w.warnings[0].filename, __file__) def test_reset_tzpath_kwarg(self): self.module.reset_tzpath(to=[f"{DRIVE}/a/b/c"])