From b86b5c7b181e24d7cb20b03cc6af389ebd97e7f5 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sat, 25 Jul 2026 08:30:51 -0700 Subject: [PATCH] fix: use ast.Constant.value in docs conf parse_version Sphinx docs failed to build on Python 3.14 with "AttributeError: 'Constant' object has no attribute 's'". The deprecated ast.Constant.s alias was removed in Python 3.14, so parse_version() in docs/source/conf.py raised while statically reading __version__ and Sphinx aborted with a ConfigError. Read node.value.value instead, which has been the canonical field since Python 3.8. Added tests/test_docs_conf.py, which extracts parse_version from conf.py (without importing sphinx) and asserts it parses a version string; it fails with the reported AttributeError before this change. --- docs/source/conf.py | 2 +- tests/test_docs_conf.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/test_docs_conf.py diff --git a/docs/source/conf.py b/docs/source/conf.py index 0604cedd..ad29b28d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -133,7 +133,7 @@ class VersionVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: if getattr(target, 'id', None) == '__version__': - self.version = node.value.s + self.version = node.value.value visitor = VersionVisitor() visitor.visit(pt) diff --git a/tests/test_docs_conf.py b/tests/test_docs_conf.py new file mode 100644 index 00000000..6f072b8a --- /dev/null +++ b/tests/test_docs_conf.py @@ -0,0 +1,31 @@ +"""Tests for helpers defined in the Sphinx configuration.""" +import ast +from os.path import dirname, exists, join + +import pytest + +CONF_FPATH = join(dirname(dirname(__file__)), 'docs', 'source', 'conf.py') + + +def _load_parse_version(): + """Extract ``parse_version`` from ``conf.py`` without importing sphinx.""" + if not exists(CONF_FPATH): + pytest.skip('docs/source/conf.py is not available') + tree = ast.parse(open(CONF_FPATH, 'r').read()) + for node in tree.body: + if isinstance(node, ast.FunctionDef) and node.name == 'parse_version': + ns = {'exists': exists, 'join': join, 'dirname': dirname} + exec(compile(ast.Module([node], []), CONF_FPATH, 'exec'), ns) + return ns['parse_version'] + pytest.skip('conf.py does not define parse_version') + + +def test_parse_version(tmp_path): + """``parse_version`` works on Python 3.14, where ``ast.Constant.s`` is gone. + + See https://github.com/pyutils/line_profiler/issues/429 + """ + parse_version = _load_parse_version() + fpath = tmp_path / 'mod.py' + fpath.write_text("__version__ = '1.2.3'\n") + assert parse_version(str(fpath)) == '1.2.3'