Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-sphinx-autodoc-typehints for
openSUSE:Factory checked in at 2026-08-22 21:36:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-sphinx-autodoc-typehints (Old)
and /work/SRC/openSUSE:Factory/.python-sphinx-autodoc-typehints.new.1258
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sphinx-autodoc-typehints"
Sat Aug 22 21:36:48 2026 rev:25 rq:1373087 version:3.13.2
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-sphinx-autodoc-typehints/python-sphinx-autodoc-typehints.changes
2026-08-01 19:51:19.071414745 +0200
+++
/work/SRC/openSUSE:Factory/.python-sphinx-autodoc-typehints.new.1258/python-sphinx-autodoc-typehints.changes
2026-08-22 21:38:52.914097595 +0200
@@ -1,0 +2,10 @@
+Sat Aug 22 14:12:25 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 3.13.2:
+ * fix: support subscripted type aliases
+- update to 3.13.1:
+ * ci(coverage): pick a measurement core that records contexts
+ * fix(resolver): ignore non-tuple __type_params__
+ * fix(resolver): stop TYPE_CHECKING and stub gaps cascading
+
+-------------------------------------------------------------------
Old:
----
sphinx_autodoc_typehints-3.13.0.tar.gz
New:
----
sphinx_autodoc_typehints-3.13.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-sphinx-autodoc-typehints.spec ++++++
--- /var/tmp/diff_new_pack.JlsR5f/_old 2026-08-22 21:38:53.554120519 +0200
+++ /var/tmp/diff_new_pack.JlsR5f/_new 2026-08-22 21:38:53.556120591 +0200
@@ -27,7 +27,7 @@
%endif
Name: python-sphinx-autodoc-typehints%{psuffix}
-Version: 3.13.0
+Version: 3.13.2
Release: 0
Summary: Type hints (PEP 484) support for the Sphinx autodoc extension
License: MIT
++++++ sphinx_autodoc_typehints-3.13.0.tar.gz ->
sphinx_autodoc_typehints-3.13.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/.github/workflows/check.yaml
new/sphinx_autodoc_typehints-3.13.2/.github/workflows/check.yaml
--- old/sphinx_autodoc_typehints-3.13.0/.github/workflows/check.yaml
2020-02-02 01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/.github/workflows/check.yaml
2020-02-02 01:00:00.000000000 +0100
@@ -31,12 +31,12 @@
steps:
- name: Install OS dependencies
run: sudo apt-get install graphviz -y
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #
v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #
v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 #
v8.3.2
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 #
v9.0.0
with:
enable-cache: false
cache-dependency-glob: "pyproject.toml"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/.github/workflows/release.yaml
new/sphinx_autodoc_typehints-3.13.2/.github/workflows/release.yaml
--- old/sphinx_autodoc_typehints-3.13.0/.github/workflows/release.yaml
2020-02-02 01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/.github/workflows/release.yaml
2020-02-02 01:00:00.000000000 +0100
@@ -10,12 +10,12 @@
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #
v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #
v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 #
v8.3.2
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 #
v9.0.0
with:
enable-cache: false
cache-dependency-glob: "pyproject.toml"
@@ -43,6 +43,6 @@
name: ${{ env.dists-artifact-name }}
path: dist/
- name: Publish to PyPI
- uses:
pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
+ uses:
pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
with:
attestations: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/.pre-commit-config.yaml
new/sphinx_autodoc_typehints-3.13.2/.pre-commit-config.yaml
--- old/sphinx_autodoc_typehints-3.13.0/.pre-commit-config.yaml 2020-02-02
01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/.pre-commit-config.yaml 2020-02-02
01:00:00.000000000 +0100
@@ -19,11 +19,11 @@
hooks:
- id: tox-toml-fmt
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: "v2.25.3"
+ rev: "v2.26.0"
hooks:
- id: pyproject-fmt
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: "v0.15.22"
+ rev: "v0.16.1"
hooks:
- id: ruff-format
- id: ruff-check
@@ -43,7 +43,7 @@
- mdformat-gfm>=1
- mdformat-ruff>=0.1.3
- repo: https://github.com/zizmorcore/zizmor-pre-commit
- rev: v1.27.0
+ rev: v1.29.0
hooks:
- id: zizmor
- repo: meta
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx_autodoc_typehints-3.13.0/PKG-INFO
new/sphinx_autodoc_typehints-3.13.2/PKG-INFO
--- old/sphinx_autodoc_typehints-3.13.0/PKG-INFO 2020-02-02
01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/PKG-INFO 2020-02-02
01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: sphinx-autodoc-typehints
-Version: 3.13.0
+Version: 3.13.2
Summary: Type hints (PEP 484) support for the Sphinx autodoc extension
Project-URL: Changelog,
https://github.com/tox-dev/sphinx-autodoc-typehints/releases
Project-URL: Homepage, https://github.com/tox-dev/sphinx-autodoc-typehints
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/_annotations.py
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/_annotations.py
---
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/_annotations.py
2020-02-02 01:00:00.000000000 +0100
+++
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/_annotations.py
2020-02-02 01:00:00.000000000 +0100
@@ -15,7 +15,7 @@
import re
import sys
import types
-from typing import TYPE_CHECKING, Any, AnyStr, ForwardRef, NewType, TypeVar,
Union
+from typing import TYPE_CHECKING, Any, AnyStr, ForwardRef, NewType, TypeVar,
Union, get_args, get_origin
from sphinx.util import rst
from sphinx.util.inspect import TypeAliasForwardRef
@@ -75,6 +75,11 @@
def __or__(self, value: Any) -> Any: # ty: ignore[invalid-method-override]
return Union[self, value] # ruff:ignore[non-pep604-annotation-union]
+ def __getitem__(self, item: Any) -> Any:
+ # A generic alias stands in for the real one while resolving string
annotations, so it has to
+ # be subscriptable: evaluating ``"Pair[int]"`` would raise
``TypeError`` and drop the hint.
+ return types.GenericAlias(self, item) # ty:
ignore[invalid-argument-type] # any origin works at runtime
+
def format_annotation(annotation: Any, config: Config, *, short_literals: bool
= False) -> str:
"""
@@ -84,17 +89,15 @@
post-order walk. The walk is driven iteratively with an explicit stack
rather than by recursion:
a PEP 695 type alias may reference itself (``type T = int | list[T]``),
which is a fully
supported recursive type but would expand forever and exhaust the
interpreter recursion limit if
- walked naively. Each frame on the stack is one suspended ``_format_node``
generator; the generator
+ walked naively -- as may a generic one through its subscripted form
(``type T[X] = X | list[T[X]]``).
+ Each frame on the stack is one suspended ``_format_node`` generator; the
generator
yields the child annotations it needs formatted and is resumed with their
rendered strings.
``active`` holds the aliases currently being expanded along the path from
the root, so when an
alias references itself it is rendered as a cross-reference to its own
name -- the natural
representation of a recursive type -- instead of being expanded again.
"""
- fully_qualified: bool = getattr(config, "typehints_fully_qualified", False)
- cycle_prefix = "" if fully_qualified else "~"
-
stack = [(_format_node(annotation, config, short_literals=short_literals),
annotation)]
- active = {id(annotation)} if isinstance(annotation, TypeAliasType) else
set()
+ active = {id(alias)} if (alias := _underlying_type_alias(annotation)) is
not None else set()
rendered: str | None = None
while True:
gen, node = stack[-1]
@@ -102,22 +105,18 @@
child = next(gen) if rendered is None else gen.send(rendered)
except StopIteration as done:
stack.pop()
- if isinstance(node, TypeAliasType):
- active.discard(id(node))
+ if (alias := _underlying_type_alias(node)) is not None:
+ active.discard(id(alias))
rendered = done.value
if not stack:
return rendered
continue
- if isinstance(child, TypeAliasType) and id(child) in active:
- # A cross-module alias may live in a module Sphinx has not read
yet, leaving it out of the py
- # domain here. Fall back to its canonical qualified name, which
resolves in the later xref
- # phase and renders the same short form when the alias is
undocumented.
- self_name = _get_canonical_type_alias_name(child) or child.__name__
- rendered = _type_alias_crossref(child, config) or
f":py:type:`{cycle_prefix}{self_name}`"
+ if (alias := _underlying_type_alias(child)) is not None and id(alias)
in active:
+ rendered = _type_alias_crossref(alias, config) or
_alias_name_reference(alias, config)
continue
stack.append((_format_node(child, config,
short_literals=short_literals), child))
- if isinstance(child, TypeAliasType):
- active.add(id(child))
+ if alias is not None:
+ active.add(id(alias))
rendered = None
@@ -174,6 +173,24 @@
return crossref
return (yield annotation.__value__)
+ if isinstance(alias := get_origin(annotation), TypeAliasType |
TypeAliasForwardRef):
+ # A subscripted generic alias, e.g. ``NDArray[np.void]`` for ``type
NDArray[ScalarT] = ...``.
+ # It is a plain ``types.GenericAlias``, so without unwrapping it here
the code below would
+ # render it as its wrapper class' name instead of as the alias.
+ args = get_args(annotation)
+ if isinstance(alias, TypeAliasForwardRef): # a documented alias,
resolved from a string annotation
+ rendered_alias = yield alias
+ elif (crossref := _type_alias_crossref(alias, config)) is not None:
+ rendered_alias = crossref
+ elif _matches_type_params(alias, args): # an undocumented alias:
expand its value, with args substituted
+ return (yield _substitute_type_params(alias, args))
+ else: # a wrong-arity subscript cannot expand, so name the alias
instead of leaking its type params
+ rendered_alias = _alias_name_reference(alias, config)
+ parts = []
+ for arg in args:
+ parts.append((yield arg)) # yield is illegal inside a
comprehension
+ return f"{rendered_alias}\\ \\[{', '.join(parts)}]"
+
try:
module = get_annotation_module(annotation)
class_name = get_annotation_class_name(annotation, module)
@@ -364,6 +381,46 @@
return isinstance(annotation, NewType)
+def _underlying_type_alias(annotation: Any) -> TypeAliasType | None:
+ """Return the PEP 695 alias an annotation is, or the one a subscripted
alias parametrizes."""
+ if isinstance(annotation, TypeAliasType):
+ return annotation
+ if isinstance(origin := get_origin(annotation), TypeAliasType):
+ return origin
+ return None
+
+
+def _matches_type_params(alias: TypeAliasType, args: tuple[Any, ...]) -> bool:
+ """Whether ``args`` can be substituted: the runtime accepts a wrong-arity
subscript, so check it here."""
+ params = alias.__type_params__
+ if any(not isinstance(param, TypeVar) for param in params): # a
TypeVarTuple or ParamSpec absorbs any count
+ return True
+ return len(params) == len(args)
+
+
+def _substitute_type_params(alias: TypeAliasType, args: tuple[Any, ...]) ->
Any:
+ """Substitute ``args`` into an undocumented generic alias' value, so it
expands to concrete types."""
+ if isinstance(value := alias.__value__, TypeVar):
+ # ``type Ident[X] = X`` has a bare type param as its value: it is the
substitution, not a container
+ return dict(zip(alias.__type_params__, args, strict=False)).get(value,
value)
+ try:
+ return value[args]
+ except TypeError: # the value ignores its type params, e.g. ``type
Count[X] = int``
+ return value
+
+
+def _alias_name_reference(alias: TypeAliasType, config: Config) -> str:
+ """
+ Render an alias as a cross-reference to its own name, without expanding
its value.
+
+ A cross-module alias may live in a module Sphinx has not read yet, leaving
it out of the py domain. Its
+ canonical qualified name resolves in the later xref phase and renders the
same short form when the alias
+ is undocumented.
+ """
+ prefix = "" if getattr(config, "typehints_fully_qualified", False) else "~"
+ return f":py:type:`{prefix}{_get_canonical_type_alias_name(alias) or
alias.__name__}`"
+
+
def _type_alias_crossref(annotation: TypeAliasType, config: Config) -> str |
None:
"""
Render a PEP 695 type alias as a reStructuredText cross-reference, or
``None`` when it is not a known target.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/_resolver/_stubs.py
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/_resolver/_stubs.py
---
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/_resolver/_stubs.py
2020-02-02 01:00:00.000000000 +0100
+++
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/_resolver/_stubs.py
2020-02-02 01:00:00.000000000 +0100
@@ -27,11 +27,15 @@
def _get_stub_context(obj: Any) -> tuple[dict[str, Any], set[str], str]:
"""
- Return (localns, alias_names, owner_module_name) from the stub owning
*obj*.
+ Return (localns, crossref_names, owner_module_name) from the stub owning
*obj*.
Single lookup avoids duplicate ``_find_stub_owner`` / ``_parse_stub_ast``
calls. The owner module name lets callers
use ``vars(sys.modules[name])`` as the correct globalns — important when a
C extension function lives in a child
module but its stub belongs to a parent package (e.g.
``cbor2._cbor2.dumps`` → ``cbor2/__init__.pyi``).
+
+ Crossref names are the stub's type aliases plus every name it imports that
has no runtime counterpart, such as
+ numpy's ``_SeriesLikeCoef_co`` living in the stub-only
``numpy/polynomial/_polytypes.pyi``. Both render as a plain
+ cross-reference instead of failing to evaluate.
"""
if (info := _find_stub_owner(obj)) is None:
return {}, set(), ""
@@ -39,8 +43,9 @@
if (tree := _parse_stub_ast(stub_path)) is None:
return {}, set(), ""
ns: dict[str, Any] = dict(vars(owner_module))
- ns.update(_resolve_stub_imports(tree, _stub_owner_package(owner_module,
stub_path)))
- return ns, _extract_type_alias_names(tree), owner_module.__name__
+ resolved, unresolved = _resolve_stub_imports(tree,
_stub_owner_package(owner_module, stub_path))
+ ns.update(resolved)
+ return ns, _extract_type_alias_names(tree) | (unresolved - ns.keys()),
owner_module.__name__
def _stub_owner_package(owner_module: types.ModuleType, stub_path: Path) ->
str:
@@ -54,22 +59,25 @@
return owner_name.rpartition(".")[0]
-def _resolve_stub_imports(tree: ast.Module, owner_package: str = "") ->
dict[str, Any]:
+def _resolve_stub_imports(tree: ast.Module, owner_package: str = "") ->
tuple[dict[str, Any], set[str]]:
ns: dict[str, Any] = {}
- _resolve_stub_imports_from_body(tree.body, owner_package, ns)
+ unresolved: set[str] = set()
+ _resolve_stub_imports_from_body(tree.body, owner_package, ns, unresolved)
_resolve_stub_definitions(tree.body, ns)
- return ns
+ return ns, unresolved - ns.keys()
-def _resolve_stub_imports_from_body(body: list[ast.stmt], owner_package: str,
ns: dict[str, Any]) -> None:
+def _resolve_stub_imports_from_body(
+ body: list[ast.stmt], owner_package: str, ns: dict[str, Any], unresolved:
set[str]
+) -> None:
for node in body:
if isinstance(node, ast.Import):
_resolve_import_node(node, ns)
elif isinstance(node, ast.ImportFrom):
- _resolve_import_from_node(node, owner_package, ns)
+ _resolve_import_from_node(node, owner_package, ns, unresolved)
elif isinstance(node, ast.If):
- _resolve_stub_imports_from_body(node.body, owner_package, ns)
- _resolve_stub_imports_from_body(node.orelse, owner_package, ns)
+ _resolve_stub_imports_from_body(node.body, owner_package, ns,
unresolved)
+ _resolve_stub_imports_from_body(node.orelse, owner_package, ns,
unresolved)
_STUB_DEFINITION_TYPES = (ast.Assign, ast.AnnAssign, ast.ClassDef,
ast.TypeAlias)
@@ -104,19 +112,26 @@
ns[top] = importlib.import_module(top)
-def _resolve_import_from_node(node: ast.ImportFrom, owner_package: str, ns:
dict[str, Any]) -> None:
- if (module_name := _resolve_import_from(node, owner_package)) is None:
- return
- try:
- mod = importlib.import_module(module_name)
- except ImportError:
- return
+def _resolve_import_from_node(
+ node: ast.ImportFrom, owner_package: str, ns: dict[str, Any], unresolved:
set[str]
+) -> None:
+ module_name = _resolve_import_from(node, owner_package)
+ mod = _import_module_or_none(module_name) if module_name else None
for alias in node.names:
if alias.name == "*":
continue
name = alias.asname or alias.name
- if (val := getattr(mod, alias.name, None)) is not None:
+ if mod is not None and (val := getattr(mod, alias.name, None)) is not
None:
ns[name] = val
+ else:
+ unresolved.add(name)
+
+
+def _import_module_or_none(module_name: str) -> types.ModuleType | None:
+ try:
+ return importlib.import_module(module_name)
+ except ImportError:
+ return None
def _resolve_import_from(node: ast.ImportFrom, owner_package: str) -> str |
None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/_resolver/_type_hints.py
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/_resolver/_type_hints.py
---
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/_resolver/_type_hints.py
2020-02-02 01:00:00.000000000 +0100
+++
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/_resolver/_type_hints.py
2020-02-02 01:00:00.000000000 +0100
@@ -2,6 +2,7 @@
from __future__ import annotations
+import ast
import contextlib
import importlib
import inspect
@@ -9,7 +10,7 @@
import sys
import textwrap
import types
-from typing import Any, get_type_hints
+from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, TypeVarTuple,
get_type_hints
from sphinx.ext.autodoc.mock import mock
from sphinx.util import logging
@@ -23,6 +24,9 @@
if sys.version_info >= (3, 14): # pragma: >=3.14 cover
import annotationlib
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
_LOGGER = logging.getLogger(__name__)
_TYPE_GUARD_IMPORT_RE = re.compile(
@@ -40,24 +44,24 @@
def get_all_type_hints(
- autodoc_mock_imports: list[str], obj: Any, name: str, localns: dict[Any,
MyTypeAliasForwardRef]
+ autodoc_mock_imports: list[str], obj: Any, name: str, localns:
Mapping[str, Any]
) -> dict[str, Any]:
result = _get_type_hint(autodoc_mock_imports, name, obj, localns)
if not result:
stub_obj = _stub_target(obj) if inspect.isclass(obj) else obj
result = backfill_type_hints(stub_obj, name)
stub_localns: dict[str, Any] = {}
- stub_alias_names: set[str] = set()
+ stub_crossref_names: set[str] = set()
stub_owner_module: str = ""
if not result:
result = _backfill_from_stub(stub_obj)
if result:
- stub_localns, stub_alias_names, stub_owner_module =
_get_stub_context(stub_obj)
+ stub_localns, stub_crossref_names, stub_owner_module =
_get_stub_context(stub_obj)
combined_localns = {**stub_localns, **localns}
- for alias_name in stub_alias_names:
- ref = MyTypeAliasForwardRef(alias_name)
+ for crossref_name in stub_crossref_names:
+ ref = MyTypeAliasForwardRef(crossref_name)
ref.crossref = True
- combined_localns[alias_name] = ref
+ combined_localns[crossref_name] = ref
try:
obj.__annotations__ = result
except (AttributeError, TypeError):
@@ -89,11 +93,11 @@
"""
if (annotation := _backfill_descriptor_annotation(obj)) is None:
return None
- localns, alias_names, owner_module = _get_stub_context(obj)
- for alias_name in alias_names:
- ref = MyTypeAliasForwardRef(alias_name)
+ localns, crossref_names, owner_module = _get_stub_context(obj)
+ for crossref_name in crossref_names:
+ ref = MyTypeAliasForwardRef(crossref_name)
ref.crossref = True
- localns[alias_name] = ref
+ localns[crossref_name] = ref
return _resolve_string_annotations(obj, {"return": annotation}, localns,
owner_module)["return"]
@@ -122,9 +126,7 @@
return resolved
-def _get_type_hint(
- autodoc_mock_imports: list[str], name: str, obj: Any, localns: dict[Any,
MyTypeAliasForwardRef]
-) -> dict[str, Any]:
+def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any,
localns: Mapping[str, Any]) -> dict[str, Any]:
_resolve_type_guarded_imports(autodoc_mock_imports, obj)
localns = _build_localns(obj, localns)
try:
@@ -197,19 +199,28 @@
def _execute_guarded_code(autodoc_mock_imports: list[str], obj: Any,
module_code: str) -> None:
for _, part in _TYPE_GUARD_IMPORT_RE.findall(module_code):
- guarded_code = textwrap.dedent(part)
try:
- _run_guarded_import(autodoc_mock_imports, obj, guarded_code)
- except Exception as exc: # ruff:ignore[blind-except]
- module_name = getattr(obj, "__module__", None) or getattr(obj,
"__name__", "?")
- _LOGGER.warning(
- "Failed guarded type import in %r: %r",
- module_name,
- exc,
- type="sphinx_autodoc_typehints",
- subtype="guarded_import",
- location=get_obj_location(obj),
- )
+ # One statement at a time, so an unimportable optional dependency
cannot strand the names after it — #741
+ statements = [ast.unparse(node) for node in
ast.parse(textwrap.dedent(part)).body]
+ except SyntaxError as exc:
+ _warn_guarded_import(obj, exc)
+ continue
+ for statement in statements:
+ try:
+ _run_guarded_import(autodoc_mock_imports, obj, statement)
+ except Exception as exc: # ruff:ignore[blind-except]
+ _warn_guarded_import(obj, exc)
+
+
+def _warn_guarded_import(obj: Any, exc: Exception) -> None:
+ _LOGGER.warning(
+ "Failed guarded type import in %r: %r",
+ getattr(obj, "__module__", None) or getattr(obj, "__name__", "?"),
+ exc,
+ type="sphinx_autodoc_typehints",
+ subtype="guarded_import",
+ location=get_obj_location(obj),
+ )
def _run_guarded_import(autodoc_mock_imports: list[str], obj: Any,
guarded_code: str) -> None:
@@ -228,12 +239,12 @@
pass
-def _build_localns(obj: Any, localns: dict[Any, MyTypeAliasForwardRef]) ->
dict[Any, MyTypeAliasForwardRef]:
- if type_params := getattr(obj, "__type_params__", None):
- localns = {**localns, **{p.__name__: p for p in type_params}}
+def _build_localns(obj: Any, localns: Mapping[str, Any]) -> dict[str, Any]:
+ result = dict(localns)
+ result.update({p.__name__: p for p in _type_params(obj)})
parts = (getattr(obj, "__qualname__", "") or "").split(".")
if len(parts) <= 1:
- return localns
+ return result
if ns := (vars(module) if (module := inspect.getmodule(obj)) else
getattr(obj, "__globals__", None)):
current: Any = None
for part in parts[:-1]:
@@ -245,10 +256,15 @@
if current is None:
break
if inspect.isclass(current):
- localns = {**localns, part: current}
- if ancestor_params := getattr(current, "__type_params__", None):
- localns = {**localns, **{p.__name__: p for p in
ancestor_params}}
- return localns
+ result[part] = current
+ result.update({p.__name__: p for p in _type_params(current)})
+ return result
+
+
+def _type_params(obj: Any) -> tuple[TypeVar | ParamSpec | TypeVarTuple, ...]:
+ # A class owning the __type_params__ slot itself (typing.TypeAliasType)
hands back the descriptor, not a tuple
+ params = getattr(obj, "__type_params__", ())
+ return params if isinstance(params, tuple) else ()
def _future_annotations_imported(obj: Any) -> bool:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/version.py
new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/version.py
--- old/sphinx_autodoc_typehints-3.13.0/src/sphinx_autodoc_typehints/version.py
2020-02-02 01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/src/sphinx_autodoc_typehints/version.py
2020-02-02 01:00:00.000000000 +0100
@@ -18,7 +18,7 @@
commit_id: str | None
__commit_id__: str | None
-__version__ = version = '3.13.0'
-__version_tuple__ = version_tuple = (3, 13, 0)
+__version__ = version = '3.13.2'
+__version_tuple__ = version_tuple = (3, 13, 2)
__commit_id__ = commit_id = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx_autodoc_typehints-3.13.0/tests/test_pep695.py
new/sphinx_autodoc_typehints-3.13.2/tests/test_pep695.py
--- old/sphinx_autodoc_typehints-3.13.0/tests/test_pep695.py 2020-02-02
01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/tests/test_pep695.py 2020-02-02
01:00:00.000000000 +0100
@@ -5,9 +5,13 @@
from pathlib import Path
from textwrap import dedent
from typing import TYPE_CHECKING, Any, Union
+from unittest.mock import create_autospec
import pytest
from conftest import normalize_sphinx_text
+from sphinx.config import Config
+
+from sphinx_autodoc_typehints import format_annotation
if TYPE_CHECKING:
from io import StringIO
@@ -527,8 +531,7 @@
monkeypatch.setitem(sys.modules, "mod_720", mod)
app.build()
assert "build succeeded" in status.getvalue()
- value = warning.getvalue().strip()
- assert not value or "Inline strong start-string without end-string" in
value
+ assert not warning.getvalue().strip()
result = normalize_sphinx_text((Path(app.srcdir) /
"_build/text/index.txt").read_text())
assert '"RecType"' in result
@@ -641,3 +644,212 @@
assert "threw an exception" not in warning.getvalue()
result = normalize_sphinx_text((Path(app.srcdir) /
"_build/text/index.txt").read_text())
assert "DiGraph[int]" in result
+
+
[email protected]("text", testroot="integration")
+def test_pep695_generic_type_alias_undocumented(
+ app: SphinxTestApp,
+ status: StringIO,
+ warning: StringIO,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """A subscripted generic alias expands to its value with the type params
substituted."""
+ mod = types.ModuleType("mod_generic_alias")
+ mod.__file__ = __file__
+ exec( # ruff:ignore[exec-builtin]
+ dedent("""\
+ from __future__ import annotations
+
+ type Pair[T] = tuple[T, T]
+
+ def pair_func(x: Pair[int]) -> None:
+ \"\"\"Describe.
+
+ :param x: a pair
+ \"\"\"
+ ...
+ """),
+ mod.__dict__,
+ )
+ (Path(app.srcdir) / "index.rst").write_text(".. autofunction::
mod_generic_alias.pair_func")
+ monkeypatch.setitem(sys.modules, "mod_generic_alias", mod)
+ app.build()
+ assert "build succeeded" in status.getvalue()
+ assert not warning.getvalue().strip()
+
+ result = normalize_sphinx_text((Path(app.srcdir) /
"_build/text/index.txt").read_text())
+ assert '"tuple"["int", "int"]' in result
+
+
[email protected]("text", testroot="integration")
+def test_pep695_generic_type_alias_documented(
+ app: SphinxTestApp,
+ status: StringIO,
+ warning: StringIO,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """A subscripted generic alias cross-references the alias and keeps its
args."""
+ mod = types.ModuleType("mod_generic_alias_doc")
+ mod.__file__ = __file__
+ exec( # ruff:ignore[exec-builtin]
+ dedent("""\
+ from __future__ import annotations
+
+ type Pair[T] = tuple[T, T]
+ \"\"\"A pair of things.\"\"\"
+
+ def pair_func(x: Pair[int]) -> None:
+ \"\"\"Describe.
+
+ :param x: a pair
+ \"\"\"
+ ...
+ """),
+ mod.__dict__,
+ )
+ (Path(app.srcdir) / "index.rst").write_text(
+ dedent("""\
+ .. py:type:: mod_generic_alias_doc.Pair
+
+ A pair of things
+
+ .. autofunction:: mod_generic_alias_doc.pair_func
+ """)
+ )
+ monkeypatch.setitem(sys.modules, "mod_generic_alias_doc", mod)
+ app.config.nitpicky = True
+ app.build()
+ assert "build succeeded" in status.getvalue()
+ assert "reference target not found" not in warning.getvalue()
+
+ result = normalize_sphinx_text((Path(app.srcdir) /
"_build/text/index.txt").read_text())
+ assert '"Pair"["int"]' in result
+
+
[email protected]("text", testroot="integration")
+def test_pep695_external_generic_type_alias(
+ app: SphinxTestApp,
+ status: StringIO,
+ warning: StringIO,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """A subscripted generic alias from another package resolves to its
canonical public name.
+
+ This is ``numpy.typing.NDArray[np.void]``: an alias defined in a private
module, re-exported from a
+ public one, used subscripted. Without unwrapping the subscription it
renders as ``GenericAlias``.
+ """
+ ext_priv_mod = types.ModuleType("extpkg2._priv")
+ exec("type ExtPair[T] = tuple[T, T]", ext_priv_mod.__dict__) #
ruff:ignore[exec-builtin]
+ ext_alias = ext_priv_mod.__dict__["ExtPair"]
+ ext_pub_mod = types.ModuleType("extpkg2")
+ ext_pub_mod.ExtPair = ext_alias # type: ignore[attr-defined]
+ monkeypatch.setitem(sys.modules, "extpkg2._priv", ext_priv_mod)
+ monkeypatch.setitem(sys.modules, "extpkg2", ext_pub_mod)
+
+ user_mod = types.ModuleType("user_mod_generic")
+ user_mod.__dict__["ExtPair"] = ext_alias
+ exec( # ruff:ignore[exec-builtin]
+ dedent("""\
+ from __future__ import annotations
+
+ def ext_pair_func(x: ExtPair[int]) -> None:
+ \"\"\"Describe.
+
+ :param x: a pair
+ \"\"\"
+ ...
+ """),
+ user_mod.__dict__,
+ )
+ (Path(app.srcdir) / "index.rst").write_text(".. autofunction::
user_mod_generic.ext_pair_func")
+ monkeypatch.setitem(sys.modules, "user_mod_generic", user_mod)
+ app.build()
+ assert "build succeeded" in status.getvalue()
+ assert "GenericAlias" not in warning.getvalue()
+
+ result = normalize_sphinx_text((Path(app.srcdir) /
"_build/text/index.txt").read_text())
+ assert '"ExtPair"["int"]' in result
+ assert "GenericAlias" not in result
+
+
[email protected]("text", testroot="integration")
+def test_recursive_generic_type_alias_does_not_recurse_forever(
+ app: SphinxTestApp,
+ status: StringIO,
+ warning: StringIO,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """A generic alias that references itself through its subscripted form
builds cleanly."""
+ mod = types.ModuleType("mod_rec_generic")
+ mod.__file__ = __file__
+ exec( # ruff:ignore[exec-builtin]
+ dedent("""\
+ from __future__ import annotations
+
+ type RecPair[T] = T | list[RecPair[T]]
+ \"\"\"A recursive generic type alias.\"\"\"
+
+ def some_func(some_param: RecPair[int]) -> None:
+ \"\"\"Describe.
+
+ :param some_param: some description
+ \"\"\"
+ ...
+ """),
+ mod.__dict__,
+ )
+ (Path(app.srcdir) / "index.rst").write_text(".. autofunction::
mod_rec_generic.some_func")
+ monkeypatch.setitem(sys.modules, "mod_rec_generic", mod)
+ app.build()
+ assert "build succeeded" in status.getvalue()
+ assert not warning.getvalue().strip()
+
+ result = normalize_sphinx_text((Path(app.srcdir) /
"_build/text/index.txt").read_text())
+ assert '"RecPair"' in result
+
+
+_mod_generic_edge = types.ModuleType("mod_generic_edge")
+_mod_generic_edge.__file__ = __file__
+exec( # ruff:ignore[exec-builtin]
+ dedent("""\
+ type Ident[X] = X
+ type Count[X] = int
+ type Nested[X] = list[Ident[X]]
+ type Pair[A, B] = tuple[A, B]
+ type Star[*Ts] = tuple[*Ts]
+ """),
+ _mod_generic_edge.__dict__,
+)
+_ALIAS_XREF = r":py:type:`~mod_generic_edge.Pair`"
+
+
[email protected](
+ ("annotation", "expected"),
+ [
+ pytest.param(_mod_generic_edge.Ident[int], r":py:class:`int`",
id="value_is_a_bare_type_param"),
+ pytest.param(_mod_generic_edge.Count[int], r":py:class:`int`",
id="value_ignores_its_type_param"),
+ pytest.param(
+ _mod_generic_edge.Nested[int],
+ r":py:class:`list`\ \[:py:class:`int`]",
+ id="value_wraps_another_alias",
+ ),
+ pytest.param(
+ _mod_generic_edge.Star[int, str],
+ r":py:class:`tuple`\ \[:py:class:`int`, :py:class:`str`]",
+ id="variadic_type_param",
+ ),
+ pytest.param(
+ _mod_generic_edge.Pair[int],
+ _ALIAS_XREF + r"\ \[:py:class:`int`]",
+ id="too_few_args",
+ ),
+ pytest.param(
+ _mod_generic_edge.Pair[int, str, bool],
+ _ALIAS_XREF + r"\ \[:py:class:`int`, :py:class:`str`,
:py:class:`bool`]",
+ id="too_many_args",
+ ),
+ ],
+)
+def test_undocumented_generic_alias_substitution(annotation: Any, expected:
str) -> None:
+ """An undocumented generic alias expands its value, unless the subscript
arity rules that out."""
+ assert format_annotation(annotation, create_autospec(Config)) == expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/tests/test_resolver/test_stubs.py
new/sphinx_autodoc_typehints-3.13.2/tests/test_resolver/test_stubs.py
--- old/sphinx_autodoc_typehints-3.13.0/tests/test_resolver/test_stubs.py
2020-02-02 01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/tests/test_resolver/test_stubs.py
2020-02-02 01:00:00.000000000 +0100
@@ -325,24 +325,26 @@
@pytest.mark.parametrize(
- ("source", "expected"),
+ ("source", "expected", "expected_unresolved"),
[
- pytest.param("import os\nimport sys\n", {"os": os, "sys": sys},
id="basic_import"),
- pytest.param("import os as operating_system\n", {"operating_system":
os}, id="import_as"),
- pytest.param("import os.path\n", {"os": os}, id="dotted_import"),
- pytest.param("from typing import Optional, Any\n", {"Optional":
Optional, "Any": Any}, id="from_import"),
- pytest.param("from typing import Optional as Opt\n", {"Opt":
Optional}, id="from_import_as"),
- pytest.param("from typing import *\n", {}, id="star_import_skipped"),
- pytest.param("import nonexistent_xyz\nfrom nonexistent_abc import
Foo\n", {}, id="missing_module_skipped"),
- pytest.param("from typing import NonExistentThing\n", {},
id="missing_attr_skipped"),
+ pytest.param("import os\nimport sys\n", {"os": os, "sys": sys}, set(),
id="basic_import"),
+ pytest.param("import os as operating_system\n", {"operating_system":
os}, set(), id="import_as"),
+ pytest.param("import os.path\n", {"os": os}, set(),
id="dotted_import"),
+ pytest.param("from typing import Optional, Any\n", {"Optional":
Optional, "Any": Any}, set(), id="from_import"),
+ pytest.param("from typing import Optional as Opt\n", {"Opt":
Optional}, set(), id="from_import_as"),
+ pytest.param("from typing import *\n", {}, set(),
id="star_import_skipped"),
+ pytest.param("import nonexistent_xyz\n", {}, set(),
id="missing_module_import_skipped"),
+ pytest.param("from nonexistent_abc import Foo\n", {}, {"Foo"},
id="missing_module_from_import"),
+ pytest.param("from typing import NonExistentThing\n", {},
{"NonExistentThing"}, id="missing_attr"),
],
)
-def test_resolve_stub_imports(source: str, expected: dict[str, Any]) -> None:
- ns = _resolve_stub_imports(ast.parse(source))
+def test_resolve_stub_imports(source: str, expected: dict[str, Any],
expected_unresolved: set[str]) -> None:
+ ns, unresolved = _resolve_stub_imports(ast.parse(source))
for key, val in expected.items():
assert ns[key] is val
if not expected:
assert ns == {}
+ assert unresolved == expected_unresolved
@pytest.mark.parametrize(
@@ -619,7 +621,7 @@
from typing_extensions import Self
""")
tree = ast.parse(source)
- ns = _resolve_stub_imports(tree, "")
+ ns, _ = _resolve_stub_imports(tree, "")
assert "os" in ns
assert "Self" in ns
@@ -632,7 +634,7 @@
x: int
""")
tree = ast.parse(source)
- ns = _resolve_stub_imports(tree, "")
+ ns, _ = _resolve_stub_imports(tree, "")
assert "_T" in ns
assert "MyDict" in ns
@@ -641,7 +643,7 @@
source = "from .typing import ColorType\nfrom typing import Any\n"
tree = ast.parse(source)
# matplotlib.typing.ColorType is a type alias, check it resolves to
something
- ns = _resolve_stub_imports(tree, "matplotlib")
+ ns, _ = _resolve_stub_imports(tree, "matplotlib")
assert "Any" in ns
assert ns["Any"] is Any
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/sphinx_autodoc_typehints-3.13.0/tests/test_resolver/test_type_hints.py
new/sphinx_autodoc_typehints-3.13.2/tests/test_resolver/test_type_hints.py
--- old/sphinx_autodoc_typehints-3.13.0/tests/test_resolver/test_type_hints.py
2020-02-02 01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/tests/test_resolver/test_type_hints.py
2020-02-02 01:00:00.000000000 +0100
@@ -2,14 +2,16 @@
import csv
import importlib
+import re
import subprocess # ruff:ignore[suspicious-subprocess-import]
import sys
import sysconfig
import types
-from collections.abc import Sequence
+from collections.abc import Callable, Iterator, Sequence
from csv import Error
+from decimal import Decimal
from pathlib import Path
-from typing import Any, Union, get_args, get_origin
+from typing import Any, TypeAliasType, Union, get_args, get_origin
from unittest.mock import MagicMock, patch
if sys.version_info >= (3, 14): # pragma: >=3.14 cover
@@ -139,6 +141,62 @@
assert "fake_mod" in str(args)
+def test_get_all_type_hints_for_class_owning_the_type_params_slot() -> None:
+ """typing.TypeAliasType hands back the __type_params__ descriptor rather
than a tuple (issue #740)."""
+ assert get_all_type_hints([], TypeAliasType, "mod.TypeAliasType", {}) == {}
+
+
[email protected]
+def guarded_module(tmp_path: Path, request: pytest.FixtureRequest) ->
Iterator[Callable[[str], types.ModuleType]]:
+ name = re.sub(r"\W", "_", request.node.name)
+ sys.path.insert(0, str(tmp_path))
+
+ def build(source: str) -> types.ModuleType:
+ (tmp_path / f"{name}.py").write_text(source)
+ return importlib.import_module(name)
+
+ yield build
+ sys.path.remove(str(tmp_path))
+ sys.modules.pop(name, None)
+
+
+def test_guarded_import_binds_names_below_an_unimportable_one(
+ guarded_module: Callable[[str], types.ModuleType],
+) -> None:
+ """An absent optional dependency must not strand the imports under it
(issue #741)."""
+ module = guarded_module(
+ "from __future__ import annotations\n"
+ "from typing import TYPE_CHECKING\n"
+ "\n"
+ "if TYPE_CHECKING:\n"
+ " from no_such_dependency import Absent\n"
+ " from decimal import Decimal\n"
+ "\n"
+ "def func(value: Decimal) -> None: ...\n"
+ )
+ assert get_all_type_hints([], module.func, f"{module.__name__}.func",
{})["value"] is Decimal
+
+
+def test_guarded_import_warns_when_the_block_does_not_parse(
+ guarded_module: Callable[[str], types.ModuleType],
+) -> None:
+ """A block truncated mid-literal by the guard regex is reported rather
than raised (issue #741)."""
+ module = guarded_module(
+ "from typing import TYPE_CHECKING\n"
+ "\n"
+ "if TYPE_CHECKING:\n"
+ ' DOC = """\n'
+ "text\n"
+ '"""\n'
+ "\n"
+ "def func(value: int) -> None: ...\n"
+ )
+ mock_logger = MagicMock()
+ with patch("sphinx_autodoc_typehints._resolver._type_hints._LOGGER",
mock_logger):
+ get_all_type_hints([], module.func, f"{module.__name__}.func", {})
+ assert "unterminated triple-quoted string literal" in
str(mock_logger.warning.call_args)
+
+
def test_build_localns_adds_ancestor_classes() -> None:
import tests.roots.test_nested_attrs_localns as mod #
ruff:ignore[import-outside-top-level]
@@ -307,6 +365,28 @@
_TYPE_GUARD_IMPORTS_RESOLVED.discard("stubpkg.mod")
+def test_get_all_type_hints_crossrefs_names_from_stub_only_modules(tmp_path:
Path) -> None:
+ """numpy's _polybase.pyi imports from _polytypes.pyi, which ships no
runtime module (issue #741)."""
+ pkg = tmp_path / "stubonlypkg"
+ pkg.mkdir()
+ (pkg / "__init__.py").write_text("")
+ (pkg / "_types.pyi").write_text("from typing import Any\n_SeriesLike =
Any\n")
+ (pkg / "poly.py").write_text("class Poly:\n def __init__(self, coef):
...\n")
+ (pkg / "poly.pyi").write_text(
+ "from ._types import _SeriesLike\nclass Poly:\n def __init__(self,
coef: _SeriesLike) -> None: ...\n"
+ )
+ sys.path.insert(0, str(tmp_path))
+ try:
+ mod = importlib.import_module("stubonlypkg.poly")
+ hint = get_all_type_hints([], mod.Poly, "stubonlypkg.poly.Poly",
{})["coef"]
+ finally:
+ sys.path.remove(str(tmp_path))
+ for name in [n for n in sys.modules if n.startswith("stubonlypkg")]:
+ del sys.modules[name]
+ assert isinstance(hint, MyTypeAliasForwardRef)
+ assert hint.name == "_SeriesLike"
+
+
@pytest.mark.skipif(sys.version_info < (3, 14), reason="annotationlib requires
Python 3.14+")
def test_get_type_hint_uses_annotationlib_on_name_error() -> None: # pragma:
>=3.14 cover
"""_get_type_hint falls back to annotationlib.get_annotations on 3.14+
NameError."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx_autodoc_typehints-3.13.0/tox.toml
new/sphinx_autodoc_typehints-3.13.2/tox.toml
--- old/sphinx_autodoc_typehints-3.13.0/tox.toml 2020-02-02
01:00:00.000000000 +0100
+++ new/sphinx_autodoc_typehints-3.13.2/tox.toml 2020-02-02
01:00:00.000000000 +0100
@@ -21,7 +21,8 @@
wheel_build_env = ".pkg"
dependency_groups = [ "test" ]
pass_env = [ "DIFF_AGAINST", "PYTEST_*" ]
-set_env = { COVERAGE_FILE = "{work_dir}/.coverage.{env_name}" }
+# sysmon, the coverage core Python 3.14+ defaults to, cannot record the
dynamic contexts --cov-context asks for
+set_env = { COVERAGE_FILE = "{work_dir}/.coverage.{env_name}", COVERAGE_CORE =
"ctrace" }
commands = [
[
"python",
@@ -83,3 +84,10 @@
package = "editable"
dependency_groups = [ "dev" ]
commands = [ [ "uv", "pip", "tree" ], [ "python", "-c", "import sys;
print(sys.executable)" ] ]
+
+# 3.15 ships no compiled coverage tracer yet, leaving the pure-Python core as
the only one recording contexts
+[env."3.15"]
+set_env = { COVERAGE_FILE = "{work_dir}/.coverage.{env_name}", COVERAGE_CORE =
"pytrace" }
+
+[env."3.15t"]
+set_env = { COVERAGE_FILE = "{work_dir}/.coverage.{env_name}", COVERAGE_CORE =
"pytrace" }