From 34bbebf615a159ab9acdef78b93fa407a8a4bb96 Mon Sep 17 00:00:00 2001 From: ellie <149747415+1borgy@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:31:15 -0500 Subject: [PATCH 1/3] fix: Add support for PEP 750 template strings --- src/griffe/__init__.py | 4 +++ src/griffe/_internal/expressions.py | 52 +++++++++++++++++++++++++++++ tests/test_nodes.py | 2 ++ 3 files changed, 58 insertions(+) diff --git a/src/griffe/__init__.py b/src/griffe/__init__.py index c06aad3a3..6bf664b6a 100644 --- a/src/griffe/__init__.py +++ b/src/griffe/__init__.py @@ -298,6 +298,7 @@ ExprFormatted, ExprGeneratorExp, ExprIfExp, + ExprInterpolation, ExprJoinedStr, ExprKeyword, ExprLambda, @@ -310,6 +311,7 @@ ExprSetComp, ExprSlice, ExprSubscript, + ExprTemplateStr, ExprTuple, ExprUnaryOp, ExprVarKeyword, @@ -453,6 +455,7 @@ "ExprFormatted", "ExprGeneratorExp", "ExprIfExp", + "ExprInterpolation", "ExprJoinedStr", "ExprKeyword", "ExprLambda", @@ -465,6 +468,7 @@ "ExprSetComp", "ExprSlice", "ExprSubscript", + "ExprTemplateStr", "ExprTuple", "ExprUnaryOp", "ExprVarKeyword", diff --git a/src/griffe/_internal/expressions.py b/src/griffe/_internal/expressions.py index 702bc56aa..33b134091 100644 --- a/src/griffe/_internal/expressions.py +++ b/src/griffe/_internal/expressions.py @@ -7,6 +7,7 @@ from __future__ import annotations import ast +import sys from dataclasses import dataclass from dataclasses import fields as getfields from enum import IntEnum, auto @@ -532,6 +533,22 @@ def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: yield from _yield(self.orelse, flat=flat, outer_precedence=precedence, is_left=False) +if sys.version_info >= (3, 14): + + @dataclass(eq=True, slots=True) + class ExprInterpolation(Expr): + """Template string interpolation like `{name}`.""" + + value: str | Expr + """Interpolated value.""" + + def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: + yield "{" + # Prevent parentheses from being added, avoiding `{(1 + 1)}` + yield from _yield(self.value, flat=flat, outer_precedence=_OperatorPrecedence.NONE) + yield "}" + + @dataclass(eq=True, slots=True) class ExprJoinedStr(Expr): """Joined strings like `f"a {b} c"`.""" @@ -915,6 +932,21 @@ def canonical_path(self) -> str: return self.left.canonical_path +if sys.version_info >= (3, 14): + + @dataclass(eq=True, slots=True) + class ExprTemplateStr(Expr): + """Template strings like `t"a {name}"`.""" + + values: Sequence[str | Expr] + """Joined values.""" + + def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: + yield "t'" + yield from _join(self.values, "", flat=flat) + yield "'" + + @dataclass(eq=True, slots=True) class ExprTuple(Expr): """Tuples like `(0, 1, 2)`.""" @@ -1213,6 +1245,10 @@ def _build_ifexp(node: ast.IfExp, parent: Module | Class, **kwargs: Any) -> Expr ) +def _build_interpolation(node: ast.Interpolation, parent: Module | Class, **kwargs: Any) -> Expr: + return ExprInterpolation(_build(node.value, parent, **kwargs)) + + def _build_joinedstr( node: ast.JoinedStr, parent: Module | Class, @@ -1311,6 +1347,14 @@ def _build_subscript( return ExprSubscript(left, slice_expr) +def _build_templatestr( + node: ast.TemplateStr, + parent: Module | Class, + **kwargs: Any, +) -> Expr: + return ExprTemplateStr([_build(value, parent, in_joined_str=True, **kwargs) for value in node.values]) + + def _build_tuple( node: ast.Tuple, parent: Module | Class, @@ -1367,6 +1411,14 @@ def __call__(self, node: Any, parent: Module | Class, **kwargs: Any) -> Expr: .. ast.UnaryOp: _build_unaryop, ast.Yield: _build_yield, ast.YieldFrom: _build_yield_from, + **( + { + ast.Interpolation: _build_interpolation, + ast.TemplateStr: _build_templatestr, + } + if sys.version_info >= (3, 14) + else {} + ), } diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 27cfcc53c..3080401a3 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +import sys from ast import PyCF_ONLY_AST import pytest @@ -51,6 +52,7 @@ "call(something=something)", # Strings. "f'a {round(key, 2)} {z}'", + *(["t'a {round(key, 2)} {z}'"] if sys.version_info >= (3, 14) else []), # Slices. "o[x]", "o[x, y]", From ec6210363b127a96a8cd7ace97ee1aa8433dc20b Mon Sep 17 00:00:00 2001 From: ellie <149747415+1borgy@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:51:54 -0500 Subject: [PATCH 2/3] Only export PEP 750 exprs when python>=3.14 --- src/griffe/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/griffe/__init__.py b/src/griffe/__init__.py index 6bf664b6a..868fc797c 100644 --- a/src/griffe/__init__.py +++ b/src/griffe/__init__.py @@ -164,6 +164,8 @@ from __future__ import annotations +import sys + from griffe._internal.agents.inspector import Inspector, inspect from griffe._internal.agents.nodes.assignments import get_instance_names, get_name, get_names from griffe._internal.agents.nodes.ast import ( @@ -298,7 +300,6 @@ ExprFormatted, ExprGeneratorExp, ExprIfExp, - ExprInterpolation, ExprJoinedStr, ExprKeyword, ExprLambda, @@ -311,7 +312,6 @@ ExprSetComp, ExprSlice, ExprSubscript, - ExprTemplateStr, ExprTuple, ExprUnaryOp, ExprVarKeyword, @@ -455,7 +455,6 @@ "ExprFormatted", "ExprGeneratorExp", "ExprIfExp", - "ExprInterpolation", "ExprJoinedStr", "ExprKeyword", "ExprLambda", @@ -468,7 +467,6 @@ "ExprSetComp", "ExprSlice", "ExprSubscript", - "ExprTemplateStr", "ExprTuple", "ExprUnaryOp", "ExprVarKeyword", @@ -609,3 +607,16 @@ "visit", "vtree", ] + +if sys.version_info >= (3, 14): + from griffe._internal.expressions import ( + ExprInterpolation, + ExprTemplateStr, + ) + + __all__.extend( + [ + "ExprInterpolation", + "ExprTemplateStr", + ], + ) From 7e2f48e9fac372b5b813dc96c06bbd8a3fd2a2f2 Mon Sep 17 00:00:00 2001 From: ellie <149747415+1borgy@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:03:11 -0500 Subject: [PATCH 3/3] Move version guards around just _build --- src/griffe/__init__.py | 19 ++------ src/griffe/_internal/expressions.py | 74 ++++++++++++++--------------- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/src/griffe/__init__.py b/src/griffe/__init__.py index 868fc797c..6bf664b6a 100644 --- a/src/griffe/__init__.py +++ b/src/griffe/__init__.py @@ -164,8 +164,6 @@ from __future__ import annotations -import sys - from griffe._internal.agents.inspector import Inspector, inspect from griffe._internal.agents.nodes.assignments import get_instance_names, get_name, get_names from griffe._internal.agents.nodes.ast import ( @@ -300,6 +298,7 @@ ExprFormatted, ExprGeneratorExp, ExprIfExp, + ExprInterpolation, ExprJoinedStr, ExprKeyword, ExprLambda, @@ -312,6 +311,7 @@ ExprSetComp, ExprSlice, ExprSubscript, + ExprTemplateStr, ExprTuple, ExprUnaryOp, ExprVarKeyword, @@ -455,6 +455,7 @@ "ExprFormatted", "ExprGeneratorExp", "ExprIfExp", + "ExprInterpolation", "ExprJoinedStr", "ExprKeyword", "ExprLambda", @@ -467,6 +468,7 @@ "ExprSetComp", "ExprSlice", "ExprSubscript", + "ExprTemplateStr", "ExprTuple", "ExprUnaryOp", "ExprVarKeyword", @@ -607,16 +609,3 @@ "visit", "vtree", ] - -if sys.version_info >= (3, 14): - from griffe._internal.expressions import ( - ExprInterpolation, - ExprTemplateStr, - ) - - __all__.extend( - [ - "ExprInterpolation", - "ExprTemplateStr", - ], - ) diff --git a/src/griffe/_internal/expressions.py b/src/griffe/_internal/expressions.py index 33b134091..6e6b9c02a 100644 --- a/src/griffe/_internal/expressions.py +++ b/src/griffe/_internal/expressions.py @@ -533,20 +533,18 @@ def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: yield from _yield(self.orelse, flat=flat, outer_precedence=precedence, is_left=False) -if sys.version_info >= (3, 14): - - @dataclass(eq=True, slots=True) - class ExprInterpolation(Expr): - """Template string interpolation like `{name}`.""" +@dataclass(eq=True, slots=True) +class ExprInterpolation(Expr): + """Template string interpolation like `{name}`.""" - value: str | Expr - """Interpolated value.""" + value: str | Expr + """Interpolated value.""" - def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: - yield "{" - # Prevent parentheses from being added, avoiding `{(1 + 1)}` - yield from _yield(self.value, flat=flat, outer_precedence=_OperatorPrecedence.NONE) - yield "}" + def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: + yield "{" + # Prevent parentheses from being added, avoiding `{(1 + 1)}` + yield from _yield(self.value, flat=flat, outer_precedence=_OperatorPrecedence.NONE) + yield "}" @dataclass(eq=True, slots=True) @@ -932,19 +930,17 @@ def canonical_path(self) -> str: return self.left.canonical_path -if sys.version_info >= (3, 14): - - @dataclass(eq=True, slots=True) - class ExprTemplateStr(Expr): - """Template strings like `t"a {name}"`.""" +@dataclass(eq=True, slots=True) +class ExprTemplateStr(Expr): + """Template strings like `t"a {name}"`.""" - values: Sequence[str | Expr] - """Joined values.""" + values: Sequence[str | Expr] + """Joined values.""" - def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: - yield "t'" - yield from _join(self.values, "", flat=flat) - yield "'" + def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]: + yield "t'" + yield from _join(self.values, "", flat=flat) + yield "'" @dataclass(eq=True, slots=True) @@ -1245,8 +1241,10 @@ def _build_ifexp(node: ast.IfExp, parent: Module | Class, **kwargs: Any) -> Expr ) -def _build_interpolation(node: ast.Interpolation, parent: Module | Class, **kwargs: Any) -> Expr: - return ExprInterpolation(_build(node.value, parent, **kwargs)) +if sys.version_info >= (3, 14): + + def _build_interpolation(node: ast.Interpolation, parent: Module | Class, **kwargs: Any) -> Expr: + return ExprInterpolation(_build(node.value, parent, **kwargs)) def _build_joinedstr( @@ -1347,12 +1345,14 @@ def _build_subscript( return ExprSubscript(left, slice_expr) -def _build_templatestr( - node: ast.TemplateStr, - parent: Module | Class, - **kwargs: Any, -) -> Expr: - return ExprTemplateStr([_build(value, parent, in_joined_str=True, **kwargs) for value in node.values]) +if sys.version_info >= (3, 14): + + def _build_templatestr( + node: ast.TemplateStr, + parent: Module | Class, + **kwargs: Any, + ) -> Expr: + return ExprTemplateStr([_build(value, parent, in_joined_str=True, **kwargs) for value in node.values]) def _build_tuple( @@ -1411,15 +1411,15 @@ def __call__(self, node: Any, parent: Module | Class, **kwargs: Any) -> Expr: .. ast.UnaryOp: _build_unaryop, ast.Yield: _build_yield, ast.YieldFrom: _build_yield_from, - **( +} + +if sys.version_info >= (3, 14): + _node_map.update( { ast.Interpolation: _build_interpolation, ast.TemplateStr: _build_templatestr, - } - if sys.version_info >= (3, 14) - else {} - ), -} + }, + ) def _build(node: ast.AST, parent: Module | Class, /, **kwargs: Any) -> Expr: