From 14f75c0e7667e6f34e5450427a0bc7b6c5a6162a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 11 Jun 2024 22:26:21 -0700 Subject: [PATCH] Update for sphinx v7.3.x (#346) * remove sys module from jinja contexts * updates for changes to python domain * support new versionremoved directive * fix typing problems * bump mypy version * switch apigen data from builder env to app instance * ensure monkeypatch is applied to sphinx/domains/python/*.py * pin libclang to < v17 on macos --- docs/admonitions.rst | 2 +- docs/apidoc/python/apigen.rst | 25 +++----------- docs/apidoc/python/index.rst | 8 ++--- docs/conf.py | 33 +++++++++++++++++-- requirements/cpp.txt | 4 ++- requirements/dev-mypy.txt | 2 +- sphinx_immaterial/apidoc/apigen_utils.py | 1 - sphinx_immaterial/apidoc/cpp/api_parser.py | 30 +++++++++-------- sphinx_immaterial/apidoc/cpp/apigen.py | 16 ++++----- sphinx_immaterial/apidoc/cpp/cppreference.py | 2 +- ..._symbol_resolution_through_type_aliases.py | 2 +- .../apidoc/cpp/macro_parameters.py | 1 + sphinx_immaterial/apidoc/cpp/symbol_ids.py | 12 ++++--- sphinx_immaterial/apidoc/json/domain.py | 6 ++-- .../apidoc/python/annotation_style.py | 10 +++++- sphinx_immaterial/apidoc/python/apigen.py | 26 +++++++-------- .../apidoc/python/attribute_style.py | 8 ++++- .../apidoc/python/autodoc_property_type.py | 7 +++- .../strip_self_and_return_type_annotations.py | 2 +- .../python/style_default_values_as_code.py | 12 +++++-- .../python/type_annotation_transforms.py | 8 ++++- sphinx_immaterial/custom_admonitions.py | 14 +++++--- sphinx_immaterial/graphviz.py | 2 +- sphinx_immaterial/kbd_keys.py | 8 ++--- tests/extra_scope_test.py | 2 +- tests/graphviz_test.py | 2 +- tests/issue_134/issue_134_test.py | 4 +-- .../python_transform_type_annotations_test.py | 16 ++++++++- tests/requirements.txt | 1 + tests/system_fonts_test.py | 2 +- 30 files changed, 170 insertions(+), 98 deletions(-) diff --git a/docs/admonitions.rst b/docs/admonitions.rst index 66c810ab2..7f14b663d 100644 --- a/docs/admonitions.rst +++ b/docs/admonitions.rst @@ -56,7 +56,7 @@ usage in other sphinx-based themes. Version Directives ****************** -The `versionadded`, `versionchanged`, and `deprecated` directives defined by Sphinx are also +The `versionadded`, `versionchanged`, `deprecated`, and `versionremoved` directives defined by Sphinx are also rendered by the sphinx-immaterial theme as admonitions. The directives are optionally overridden to support additional :rst:dir:`admonition` options :rst:`:collapsible:`, :rst:`:name:`, and :rst:`:class:`. diff --git a/docs/apidoc/python/apigen.rst b/docs/apidoc/python/apigen.rst index 157007612..2257782dd 100644 --- a/docs/apidoc/python/apigen.rst +++ b/docs/apidoc/python/apigen.rst @@ -306,24 +306,7 @@ Configuration The following generated documents will be used (depending on the value of :confval:`python_apigen_case_insensitive_filesystem`): - .. jinja:: sys - - {%- set example_python_apigen_modules = { - "my_module": "my_api/", - "my_other_module": "other_api/my_other_module.", - } - %} - {%- set example_python_apigen_objects = [ - ("my_module.foo", ""), - ("my_module.Foo", ""), - ("my_module.Foo.method", ""), - ("my_module.Foo.__init__", "json"), - ("my_module.Foo.__init__", "values"), - ("my_module.Bar", ""), - ("my_other_module.Baz", ""), - ] - %} - {%- set python_apigen_get_docname = sys.modules["sphinx_immaterial.apidoc.python.apigen"]._get_docname %} + .. jinja:: python_apigen_path_examples .. list-table:: :widths: auto @@ -334,11 +317,11 @@ Configuration - Document (case-sensitive) - Document (case-insensitive) - {%- for full_name, overload_id in example_python_apigen_objects %} + {%- for full_name, overload_id, case_sensitive, case_insensitive in example_python_apigen_objects %} * - :python:`{{ full_name }}` - {{ "``" + overload_id + "``" if overload_id else "" }} - - :file:`{{ python_apigen_get_docname(example_python_apigen_modules, full_name, overload_id, False) }}` - - :file:`{{ python_apigen_get_docname(example_python_apigen_modules, full_name, overload_id, True) }}` + - :file:`{{ case_sensitive }}` + - :file:`{{ case_insensitive }}` {%- endfor %} .. note:: diff --git a/docs/apidoc/python/index.rst b/docs/apidoc/python/index.rst index 1c92e0fce..9a19466cd 100644 --- a/docs/apidoc/python/index.rst +++ b/docs/apidoc/python/index.rst @@ -49,9 +49,9 @@ Python domain customization Specifies whether to add the following mappings to :confval:`python_type_aliases`: - .. jinja:: sys + .. jinja:: typing_names - {%- for x in sys.modules["sphinx_immaterial.apidoc.python.type_annotation_transforms"].TYPING_NAMES %} + {%- for x in TYPING_NAMES %} - :python:`{{ x }}` -> :python:`typing.{{ x }}` {%- endfor %} @@ -63,9 +63,9 @@ Python domain customization Specifies whether to add the following :pep:`585` mappings to :confval:`python_type_aliases`: - .. jinja:: sys + .. jinja:: pep685_aliases - {%- for k, v in sys.modules["sphinx_immaterial.apidoc.python.type_annotation_transforms"].PEP585_ALIASES.items() %} + {%- for k, v in aliases.items() %} - :python:`{{ k }}` -> :python:`{{ v }}` {% endfor %} diff --git a/docs/conf.py b/docs/conf.py index c8894ca17..6f441ffac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,10 @@ from sphinx_immaterial.apidoc import ( object_description_options as _object_description_options, ) +from sphinx_immaterial.apidoc.python import ( + type_annotation_transforms, + apigen as python_apigen, +) logger = sphinx.util.logging.getLogger(__name__) @@ -349,9 +353,34 @@ def get_colors(color_t: str): unique_colors.append(m.group(1)) return unique_colors - +# jinja contexts +example_python_apigen_modules = { + "my_module": "my_api/", + "my_other_module": "other_api/my_other_module.", +} +example_python_apigen_objects = [ + ("my_module.foo", ""), + ("my_module.Foo", ""), + ("my_module.Foo.method", ""), + ("my_module.Foo.__init__", "json"), + ("my_module.Foo.__init__", "values"), + ("my_module.Bar", ""), + ("my_other_module.Baz", ""), +] jinja_contexts = { - "sys": {"sys": sys}, + "python_apigen_path_examples": { + "example_python_apigen_objects": [ + ( + full_name, + overload_id, + python_apigen._get_docname(example_python_apigen_modules, full_name, overload_id, False), + python_apigen._get_docname(example_python_apigen_modules, full_name, overload_id, True), + ) + for full_name, overload_id in example_python_apigen_objects + ], + }, + "typing_names": {"TYPING_NAMES": type_annotation_transforms.TYPING_NAMES}, + "pep685_aliases": {"aliases": type_annotation_transforms.PEP585_ALIASES}, "colors": { "supported_primary": get_colors("primary"), "supported_accent": get_colors("accent"), diff --git a/requirements/cpp.txt b/requirements/cpp.txt index add49efad..c630110d7 100644 --- a/requirements/cpp.txt +++ b/requirements/cpp.txt @@ -1 +1,3 @@ -libclang +# https://github.com/sighingnow/libclang/issues/71 +libclang < 17 ; sys_platform == 'darwin' +libclang ; sys_platform != 'darwin' diff --git a/requirements/dev-mypy.txt b/requirements/dev-mypy.txt index fa14604f4..8d1588dd2 100644 --- a/requirements/dev-mypy.txt +++ b/requirements/dev-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.8.0 +mypy==1.9.0 types-PyYAML docutils-stubs types-beautifulsoup4 diff --git a/sphinx_immaterial/apidoc/apigen_utils.py b/sphinx_immaterial/apidoc/apigen_utils.py index 7979a66d6..62e46d95e 100644 --- a/sphinx_immaterial/apidoc/apigen_utils.py +++ b/sphinx_immaterial/apidoc/apigen_utils.py @@ -89,7 +89,6 @@ def case_insensitive_filesystem(self) -> bool: def clear_existing_generated_files(self): srcdir = self.app.srcdir for output_prefix in self.output_prefixes: - glob_pattern = os.path.join(srcdir, output_prefix) for p in glob.glob( os.path.join(srcdir, output_prefix + "*.rst"), recursive=True ): diff --git a/sphinx_immaterial/apidoc/cpp/api_parser.py b/sphinx_immaterial/apidoc/cpp/api_parser.py index 21ad084de..4ecde4a0f 100644 --- a/sphinx_immaterial/apidoc/cpp/api_parser.py +++ b/sphinx_immaterial/apidoc/cpp/api_parser.py @@ -1153,7 +1153,10 @@ def _substitute_name( name_substitute_with_args += str(template_args) template_prefix = "" - if top_ast.templatePrefix is not None: + if ( + top_ast.templatePrefix is not None + and top_ast.templatePrefix.templates is not None + ): template_prefix = str(top_ast.templatePrefix.templates[-1]) ast.name = _parse_name(name_substitute_with_args, template_prefix=template_prefix) @@ -1348,20 +1351,21 @@ def _transform_unexposed_decl(config: Config, decl: Cursor) -> Optional[VarEntit assert initializer is not None if _is_internal_initializer(config, initializer): initializer = None - + template_params = [] + templates = cast( + sphinx.domains.cpp.ASTTemplateDeclarationPrefix, ast.templatePrefix + ).templates + assert templates is not None + for templ_param in templates[-1].params: + template_params.append( + _sphinx_ast_template_parameter_to_json( + config, cast(sphinx.domains.cpp.ASTTemplateParam, templ_param) + ) + ) obj: VarEntity = { "kind": "var", "name": name, - "template_parameters": [ - _sphinx_ast_template_parameter_to_json( - config, cast(sphinx.domains.cpp.ASTTemplateParam, t) - ) - for t in cast( - sphinx.domains.cpp.ASTTemplateDeclarationPrefix, ast.templatePrefix - ) - .templates[-1] - .params - ], + "template_parameters": template_params, "declaration": decl_string, "name_substitute": name_substitute, "initializer": initializer, @@ -1912,7 +1916,7 @@ def _normalize_requires_terms(terms: List[str]) -> List[str]: new_terms = [] def process( - expr: Union[sphinx.domains.cpp.ASTType, sphinx.domains.cpp.ASTExpression] + expr: Union[sphinx.domains.cpp.ASTType, sphinx.domains.cpp.ASTExpression], ): while True: if isinstance(expr, sphinx.domains.cpp.ASTParenExpr): diff --git a/sphinx_immaterial/apidoc/cpp/apigen.py b/sphinx_immaterial/apidoc/cpp/apigen.py index dcfdf1fa6..05a2fdd70 100644 --- a/sphinx_immaterial/apidoc/cpp/apigen.py +++ b/sphinx_immaterial/apidoc/cpp/apigen.py @@ -141,7 +141,7 @@ def _get_cpp_api_data( env: sphinx.environment.BuildEnvironment, warn: bool = False ) -> CppApiData: KEY = "_sphinx_immaterial_cpp_apigen_data" - data = getattr(env, KEY, None) + data = getattr(env.app, KEY, None) if data is not None: return data @@ -179,7 +179,7 @@ def _get_cpp_api_data( location=api_parser.json_location_to_string(diag["location"]), ) - setattr(env, KEY, data) + setattr(env.app, KEY, data) return data @@ -483,7 +483,7 @@ def _add_entity_description( summary: bool, state: docutils.parsers.rst.states.RSTState, ) -> None: - kind = entity["kind"] + # kind = entity["kind"] out = docutils.statemachine.StringList() location = entity["location"] parent_id = entity.get("parent") @@ -493,12 +493,12 @@ def _add_entity_description( include_scope = True if parent_id: - parent = api_data.entities[parent_id] + # parent = api_data.entities[parent_id] scope_template_prefix = _format_template_prefix( api_data, api_data.entities[parent_id], summary=False ) else: - parent = None + # parent = None scope_template_prefix = "" scope = api_data.get_entity_scope(entity).rstrip(":") @@ -637,7 +637,7 @@ def _add_enumerators( obj_content: sphinx.addnodes.desc_content, state: docutils.parsers.rst.states.RSTState, ) -> None: - parent_object_name = api_data.get_entity_object_name(entity) + # parent_object_name = api_data.get_entity_object_name(entity) for child in entity["enumerators"]: name = child["name"] location = child["location"] @@ -766,7 +766,7 @@ def run(self) -> List[docutils.nodes.Node]: section = docutils.nodes.section() section["ids"].append("") - # Sphinx treates the first child of a `section` node as the title, + # Sphinx treats the first child of a `section` node as the title, # regardless of its type. We use a comment node to avoid adding a title # that would be redundant with the object description. comment_placeholder = docutils.nodes.comment("", "") @@ -953,7 +953,7 @@ def setup(app: sphinx.application.Sphinx): app.add_config_value( "cpp_apigen_case_insensitive_filesystem", default=None, - types=(bool, type(None)), + types=(bool, type(None)), # type: ignore[arg-type] rebuild="env", ) app.add_config_value( diff --git a/sphinx_immaterial/apidoc/cpp/cppreference.py b/sphinx_immaterial/apidoc/cpp/cppreference.py index 0d62c8a40..53804341d 100644 --- a/sphinx_immaterial/apidoc/cpp/cppreference.py +++ b/sphinx_immaterial/apidoc/cpp/cppreference.py @@ -190,7 +190,7 @@ def setup(app: sphinx.application.Sphinx): app.add_config_value( name="cppreference_xml_files", default=None, - types=(typing.Optional[typing.List[typing.Tuple[str, str]]],), + types=(typing.Optional[typing.List[typing.Tuple[str, str]]],), # type: ignore[arg-type] rebuild="env", ) return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/sphinx_immaterial/apidoc/cpp/fix_cpp_domain_symbol_resolution_through_type_aliases.py b/sphinx_immaterial/apidoc/cpp/fix_cpp_domain_symbol_resolution_through_type_aliases.py index d980a40ef..d44288ebd 100644 --- a/sphinx_immaterial/apidoc/cpp/fix_cpp_domain_symbol_resolution_through_type_aliases.py +++ b/sphinx_immaterial/apidoc/cpp/fix_cpp_domain_symbol_resolution_through_type_aliases.py @@ -115,7 +115,7 @@ def _find_named_symbols( results = orig_find_named_symbols( self, identOrOp, - None, + None, # type: ignore[arg-type] None, # type: ignore[arg-type] templateShorthand, matchSelf, diff --git a/sphinx_immaterial/apidoc/cpp/macro_parameters.py b/sphinx_immaterial/apidoc/cpp/macro_parameters.py index de7489617..7683d9d48 100644 --- a/sphinx_immaterial/apidoc/cpp/macro_parameters.py +++ b/sphinx_immaterial/apidoc/cpp/macro_parameters.py @@ -39,6 +39,7 @@ def get_id( self: ASTMacroParameter, version: int, objectType: str, symbol: CSymbol ) -> str: # the anchor will be our parent + assert symbol.parent is not None declaration = symbol.parent.declaration assert declaration is not None return declaration.get_id(version, prefixed=False) diff --git a/sphinx_immaterial/apidoc/cpp/symbol_ids.py b/sphinx_immaterial/apidoc/cpp/symbol_ids.py index cc8b2caf9..c6e248e8a 100644 --- a/sphinx_immaterial/apidoc/cpp/symbol_ids.py +++ b/sphinx_immaterial/apidoc/cpp/symbol_ids.py @@ -15,7 +15,7 @@ def _monkey_patch_override_ast_id( ast_declaration_class: Union[ Type[sphinx.domains.c.ASTDeclaration], Type[sphinx.domains.cpp.ASTDeclaration] - ] + ], ): """Allows the Symbol id to be overridden.""" orig_get_id = ast_declaration_class.get_id @@ -34,9 +34,11 @@ def get_id( def get_symbol_anchor( - symbol: Union[sphinx.domains.c.Symbol, sphinx.domains.cpp.Symbol] + symbol: Union[sphinx.domains.c.Symbol, sphinx.domains.cpp.Symbol], ) -> str: - anchor = getattr(symbol.declaration, ANCHOR_ATTR, None) + anchor = None + if symbol.declaration is not None: + anchor = getattr(symbol.declaration, ANCHOR_ATTR, None) if anchor is None: assert symbol.declaration is not None anchor = symbol.declaration.get_newest_id() @@ -144,6 +146,8 @@ def add_target_and_index( node_id = self.options.get("node-id") if node_id is not None: symbol = ast.symbol + assert symbol is not None + assert symbol.declaration is not None setattr(symbol.declaration, ANCHOR_ATTR, node_id) symbol.docname = self.env.docname if ast is self.names[0] and node_id: @@ -157,7 +161,7 @@ def add_target_and_index( orig_run = object_class.run def run( - self: Union[sphinx.domains.c.CObject, sphinx.domains.cpp.CPPObject] + self: Union[sphinx.domains.c.CObject, sphinx.domains.cpp.CPPObject], ) -> List[docutils.nodes.Node]: result = orig_run(self) # type: ignore[arg-type] diff --git a/sphinx_immaterial/apidoc/json/domain.py b/sphinx_immaterial/apidoc/json/domain.py index bb156b139..c0d0789db 100644 --- a/sphinx_immaterial/apidoc/json/domain.py +++ b/sphinx_immaterial/apidoc/json/domain.py @@ -465,7 +465,7 @@ class JsonSchemaDirective(sphinx.directives.ObjectDescription): objtype = "schema" - option_spec = { + option_spec = { # type: ignore[misc] "fully_qualified_name": str, "title": str, "toc_title": str, @@ -1147,9 +1147,7 @@ def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: else OBJECT_PRIORITY_UNIMPORTANT, ) - def merge_domaindata( - self, docnames: List[str], otherdata: Dict - ) -> None: # pylint: disable=g-bare-generic + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # pylint: disable=g-bare-generic self.schemas.update(otherdata["schemas"]) def _find_schema( diff --git a/sphinx_immaterial/apidoc/python/annotation_style.py b/sphinx_immaterial/apidoc/python/annotation_style.py index e0053afa3..334455fa7 100644 --- a/sphinx_immaterial/apidoc/python/annotation_style.py +++ b/sphinx_immaterial/apidoc/python/annotation_style.py @@ -3,6 +3,8 @@ import docutils.nodes import sphinx.domains.python import sphinx.environment +import sphinx +import sphinx.addnodes def ensure_wrapped_in_desc_type( @@ -18,13 +20,19 @@ def _monkey_patch_python_parse_annotation(): This allows them to be distinguished from parameter names in CSS rules. """ - orig_parse_annotation = sphinx.domains.python._parse_annotation + if sphinx.version_info >= (7, 3): + orig_parse_annotation = sphinx.domains.python._annotations._parse_annotation # type: ignore[attr-defined] + else: + orig_parse_annotation = sphinx.domains.python._parse_annotation def parse_annotation( annotation: str, env: Optional[sphinx.environment.BuildEnvironment] = None ) -> List[docutils.nodes.Node]: return ensure_wrapped_in_desc_type(orig_parse_annotation(annotation, env)) # type: ignore[arg-type] + if sphinx.version_info >= (7, 3): + sphinx.domains.python._annotations._parse_annotation = parse_annotation # type: ignore[attr-defined] + sphinx.domains.python._object._parse_annotation = parse_annotation # type: ignore[attr-defined] sphinx.domains.python._parse_annotation = parse_annotation diff --git a/sphinx_immaterial/apidoc/python/apigen.py b/sphinx_immaterial/apidoc/python/apigen.py index 166b1f97a..c6e7469ac 100644 --- a/sphinx_immaterial/apidoc/python/apigen.py +++ b/sphinx_immaterial/apidoc/python/apigen.py @@ -39,7 +39,6 @@ import sphinx import sphinx.addnodes import sphinx.application -import sphinx.domains.python import sphinx.environment import sphinx.ext.autodoc import sphinx.ext.autodoc.directive @@ -60,6 +59,11 @@ else: stringify_annotation = sphinx.util.typing.stringify # type: ignore[attr-defined] +if sphinx.version_info >= (7, 3): + from sphinx.domains.python._annotations import _parse_annotation # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module +else: + from sphinx.domains.python import _parse_annotation + logger = sphinx.util.logging.getLogger(__name__) _UNCONDITIONALLY_DOCUMENTED_MEMBERS = frozenset( @@ -504,7 +508,7 @@ def _clean_class_getitem_signature(signode: sphinx.addnodes.desc_signature) -> N def _get_api_data( env: sphinx.environment.BuildEnvironment, ) -> _ApiData: - return getattr(env, "_sphinx_immaterial_python_apigen_data") + return getattr(env.app, "_sphinx_immaterial_python_apigen_data") def _generate_entity_desc_node( @@ -567,7 +571,7 @@ def object_description_transform( if i != 0: signode += sphinx.addnodes.desc_sig_punctuation("", ",") signode += sphinx.addnodes.desc_sig_space() - signode += sphinx.domains.python._parse_annotation(base_class, env) + signode += _parse_annotation(base_class, env) signode += sphinx.addnodes.desc_sig_punctuation("", ")") if callback is not None: @@ -1638,7 +1642,7 @@ def _builder_inited(app: sphinx.application.Sphinx) -> None: data = _ApiData() - setattr(env, "_sphinx_immaterial_python_apigen_data", data) + setattr(app, "_sphinx_immaterial_python_apigen_data", data) apigen_modules = app.config.python_apigen_modules if not apigen_modules: @@ -1732,9 +1736,7 @@ def _monkey_patch_napoleon_to_add_group_field(): def parse_section( self: sphinx.ext.napoleon.docstring.GoogleDocstring, section: str ) -> List[str]: - lines = self._strip_empty( - self._consume_to_next_section() - ) # pylint: disable=protected-access + lines = self._strip_empty(self._consume_to_next_section()) # pylint: disable=protected-access lines = self._dedent(lines) # pylint: disable=protected-access name = section.lower() if len(lines) != 1: @@ -1745,12 +1747,8 @@ def load_custom_sections( self: sphinx.ext.napoleon.docstring.GoogleDocstring, ) -> None: orig_load_custom_sections(self) - self._sections["group"] = lambda section: parse_section( - self, section - ) # pylint: disable=protected-access - self._sections["order"] = lambda section: parse_section( - self, section - ) # pylint: disable=protected-access + self._sections["group"] = lambda section: parse_section(self, section) # pylint: disable=protected-access + self._sections["order"] = lambda section: parse_section(self, section) # pylint: disable=protected-access sphinx.ext.napoleon.docstring.GoogleDocstring._load_custom_sections = ( # type: ignore[assignment] load_custom_sections # pylint: disable=protected-access @@ -1809,7 +1807,7 @@ def setup(app: sphinx.application.Sphinx): app.add_config_value( "python_apigen_case_insensitive_filesystem", default=None, - types=(bool, type(None)), + types=(bool, type(None)), # type: ignore[arg-type] rebuild="env", ) app.add_config_value( diff --git a/sphinx_immaterial/apidoc/python/attribute_style.py b/sphinx_immaterial/apidoc/python/attribute_style.py index 8229cdb59..8540d10eb 100644 --- a/sphinx_immaterial/apidoc/python/attribute_style.py +++ b/sphinx_immaterial/apidoc/python/attribute_style.py @@ -1,9 +1,15 @@ from typing import Type, Tuple import docutils.nodes +import sphinx import sphinx.addnodes import sphinx.domains.python +if sphinx.version_info >= (7, 3): + from sphinx.domains.python._annotations import _parse_annotation # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module +else: + from sphinx.domains.python import _parse_annotation + def _monkey_patch_pyattribute_handle_signature( directive_cls: Type[sphinx.domains.python.PyObject], @@ -17,7 +23,7 @@ def handle_signature( typ = self.options.get("type") if typ: signode += sphinx.addnodes.desc_sig_punctuation("", " : ") - signode += sphinx.domains.python._parse_annotation(typ, self.env) + signode += _parse_annotation(typ, self.env) value = self.options.get("value") if value: diff --git a/sphinx_immaterial/apidoc/python/autodoc_property_type.py b/sphinx_immaterial/apidoc/python/autodoc_property_type.py index f82639c39..9cf385665 100644 --- a/sphinx_immaterial/apidoc/python/autodoc_property_type.py +++ b/sphinx_immaterial/apidoc/python/autodoc_property_type.py @@ -114,7 +114,12 @@ def handle_signature( typ = self.options.get("type") if typ: signode += sphinx.addnodes.desc_sig_punctuation("", " : ") - signode += sphinx.domains.python._parse_annotation(typ, self.env) + if sphinx.version_info >= (7, 3): + signode += sphinx.domains.python._annotations._parse_annotation( + typ, self.env + ) + else: + signode += sphinx.domains.python._parse_annotation(typ, self.env) return fullname, prefix diff --git a/sphinx_immaterial/apidoc/python/strip_self_and_return_type_annotations.py b/sphinx_immaterial/apidoc/python/strip_self_and_return_type_annotations.py index ca407a426..163d18bcf 100644 --- a/sphinx_immaterial/apidoc/python/strip_self_and_return_type_annotations.py +++ b/sphinx_immaterial/apidoc/python/strip_self_and_return_type_annotations.py @@ -63,7 +63,7 @@ def setup(app: sphinx.application.Sphinx): "python_strip_return_type_annotations", default=r".*.(__setitem__|__init__)", rebuild="env", - types=(re.Pattern, type(None)), + types=(re.Pattern, type(None)), # type: ignore[arg-type] ) app.connect("config-inited", _config_inited) diff --git a/sphinx_immaterial/apidoc/python/style_default_values_as_code.py b/sphinx_immaterial/apidoc/python/style_default_values_as_code.py index 3966deddc..897870ec7 100644 --- a/sphinx_immaterial/apidoc/python/style_default_values_as_code.py +++ b/sphinx_immaterial/apidoc/python/style_default_values_as_code.py @@ -1,4 +1,5 @@ import docutils.nodes +import sphinx import sphinx.domains.python import sphinx.environment import sphinx.addnodes @@ -7,7 +8,10 @@ def _monkey_patch_python_parse_arglist(): """Ensures default values in signatures are styled as code.""" - orig_parse_arglist = sphinx.domains.python._parse_arglist + if sphinx.version_info >= (7, 3): + orig_parse_arglist = sphinx.domains.python._annotations._parse_arglist # type: ignore[attr-defined] + else: + orig_parse_arglist = sphinx.domains.python._parse_arglist def parse_arglist( arglist: str, *args, **kwargs @@ -25,7 +29,11 @@ def parse_arglist( ) return result - sphinx.domains.python._parse_arglist = parse_arglist + if sphinx.version_info >= (7, 3): + sphinx.domains.python._annotations._parse_arglist = parse_arglist # type: ignore[attr-defined] + sphinx.domains.python._object._parse_arglist = parse_arglist # type: ignore[attr-defined] + else: + sphinx.domains.python._parse_arglist = parse_arglist _monkey_patch_python_parse_arglist() diff --git a/sphinx_immaterial/apidoc/python/type_annotation_transforms.py b/sphinx_immaterial/apidoc/python/type_annotation_transforms.py index 761663caa..dcbf77c92 100644 --- a/sphinx_immaterial/apidoc/python/type_annotation_transforms.py +++ b/sphinx_immaterial/apidoc/python/type_annotation_transforms.py @@ -248,7 +248,10 @@ def visit_Subscript(self, node: ast.Subscript) -> ast.AST: def _monkey_patch_python_domain_to_transform_type_annotations(): - orig_parse_annotation = sphinx.domains.python._parse_annotation + if sphinx.version_info >= (7, 3): + orig_parse_annotation = sphinx.domains.python._annotations._parse_annotation # type: ignore[attr-defined] + else: + orig_parse_annotation = sphinx.domains.python._parse_annotation def _parse_annotation(annotation: str, env: sphinx.environment.BuildEnvironment): transformer_config = getattr(env, _CONFIG_ATTR, None) @@ -266,6 +269,9 @@ def _parse_annotation(annotation: str, env: sphinx.environment.BuildEnvironment) annotation = ast_unparse(tree) return orig_parse_annotation(annotation, env) + if sphinx.version_info >= (7, 3): + sphinx.domains.python._annotations._parse_annotation = _parse_annotation # type: ignore[assignment,attr-defined] + sphinx.domains.python._object._parse_annotation = _parse_annotation # type: ignore[assignment,attr-defined] sphinx.domains.python._parse_annotation = _parse_annotation # type: ignore[assignment] diff --git a/sphinx_immaterial/custom_admonitions.py b/sphinx_immaterial/custom_admonitions.py index 12054f7f1..89ec1c424 100644 --- a/sphinx_immaterial/custom_admonitions.py +++ b/sphinx_immaterial/custom_admonitions.py @@ -9,6 +9,7 @@ import jinja2 import pydantic from pydantic_extra_types.color import Color +import sphinx import sphinx.addnodes from sphinx.application import Sphinx from sphinx.config import Config @@ -42,9 +43,7 @@ _CUSTOM_ADMONITIONS_KEY = "sphinx_immaterial_custom_admonitions" -CSSClassType = Annotated[ - str, pydantic.AfterValidator(nodes.make_id) -] +CSSClassType = Annotated[str, pydantic.AfterValidator(nodes.make_id)] # defaults used for version directives re-styling VERSION_DIR_STYLE = { @@ -60,6 +59,13 @@ }, "deprecated": {"icon": "material/delete", "color": (203, 70, 83), "classes": []}, } +if sphinx.version_info >= (7, 3): + # re-use deprecated style for versionremoved directive except with different icon + VERSION_DIR_STYLE["versionremoved"] = { + "icon": "material/close", + "color": (203, 70, 83), + "classes": [], + } class CustomAdmonitionConfig(pydantic.BaseModel): @@ -237,7 +243,7 @@ class CustomVersionChange(VersionChange): """Derivative of the original version directives to add theme-specific admonition options""" - option_spec = { + option_spec = { # type: ignore[misc] "collapsible": directives.unchanged, "class": directives.class_option, "name": directives.unchanged, diff --git a/sphinx_immaterial/graphviz.py b/sphinx_immaterial/graphviz.py index 6ce0681f6..895a0f9c2 100644 --- a/sphinx_immaterial/graphviz.py +++ b/sphinx_immaterial/graphviz.py @@ -343,7 +343,7 @@ def replace_var_in_code(m: re.Match) -> str: errors = errors.strip() if errors or dot_result.returncode != 0: error_func = logger.warning if dot_result.returncode == 0 else logger.error - error_func( + error_func( # type: ignore[operator] "Error running %r with env %r: %s", dot_cmd, env, errors, location=node ) if dot_result.returncode != 0: diff --git a/sphinx_immaterial/kbd_keys.py b/sphinx_immaterial/kbd_keys.py index 5356db206..52ae988e2 100644 --- a/sphinx_immaterial/kbd_keys.py +++ b/sphinx_immaterial/kbd_keys.py @@ -83,10 +83,10 @@ def _config_inited(app: Sphinx, config: Config) -> None: def setup(app: Sphinx): - app.add_config_value("keys_class", "keys", rebuild=True, types=str) - app.add_config_value("keys_strict", False, rebuild=True, types=bool) - app.add_config_value("keys_separator", "+", rebuild=True, types=str) - app.add_config_value("keys_map", {}, rebuild=True, types=dict) + app.add_config_value("keys_class", "keys", rebuild="env", types=str) + app.add_config_value("keys_strict", False, rebuild="env", types=bool) + app.add_config_value("keys_separator", "+", rebuild="env", types=str) + app.add_config_value("keys_map", {}, rebuild="env", types=dict) app.add_role("keys", keys_role) app.connect("config-inited", _config_inited) app.add_node( diff --git a/tests/extra_scope_test.py b/tests/extra_scope_test.py index 48f3b9b03..a53b9a05b 100644 --- a/tests/extra_scope_test.py +++ b/tests/extra_scope_test.py @@ -49,7 +49,7 @@ def test_extra_scope( ) app.build() - assert not app._warning.getvalue() + assert not app._warning.getvalue() # type: ignore[attr-defined] with open(Path(app.outdir) / "index.html", mode="r") as file: soup = BeautifulSoup(file.read(), "html.parser") diff --git a/tests/graphviz_test.py b/tests/graphviz_test.py index db3d84359..0827b5c5f 100644 --- a/tests/graphviz_test.py +++ b/tests/graphviz_test.py @@ -57,4 +57,4 @@ def test_square_brackets(immaterial_make_app, graph: str): ) app.build() - assert not app._warning.getvalue() + assert not app._warning.getvalue() # type: ignore[attr-defined] diff --git a/tests/issue_134/issue_134_test.py b/tests/issue_134/issue_134_test.py index 67ec829a9..c89efc607 100644 --- a/tests/issue_134/issue_134_test.py +++ b/tests/issue_134/issue_134_test.py @@ -32,5 +32,5 @@ def test_autoclass_members(immaterial_make_app): extra_conf='extensions.append("sphinx.ext.autodoc")\n', ) app.build() - assert not app._warning.getvalue() - print(app._status.getvalue()) + assert not app._warning.getvalue() # type: ignore[attr-defined] + print(app._status.getvalue()) # type: ignore[attr-defined] diff --git a/tests/python_transform_type_annotations_test.py b/tests/python_transform_type_annotations_test.py index 68434407c..5ca2d32b2 100644 --- a/tests/python_transform_type_annotations_test.py +++ b/tests/python_transform_type_annotations_test.py @@ -41,5 +41,19 @@ def test_transform_type_annotations_pep604(theme_make_app): ("Literal[1, 2, None]", "1 | 2 | None"), ]: parent = docutils.nodes.TextElement("", "") - parent.extend(sphinx.domains.python._parse_annotation(annotation, app.env)) + + parsed_annotations = sphinx.domains.python._parse_annotation( + annotation, app.env + ) + if sphinx.version_info >= (7, 3): + as_text = "".join([n.astext() for n in parsed_annotations]) + og_parsed = sphinx.domains.python._annotations._parse_annotation( # type: ignore[module-not-found,attr-defined] + annotation, app.env + ) + assert as_text == "".join([n.astext() for n in og_parsed]) + re_exported = sphinx.domains.python._object._parse_annotation( # type: ignore[module-not-found,attr-defined] + annotation, app.env + ) + assert as_text == "".join([n.astext() for n in re_exported]) + parent.extend(parsed_annotations) assert parent.astext() == expected_text diff --git a/tests/requirements.txt b/tests/requirements.txt index 4b24f83c2..f22d04909 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,6 +2,7 @@ coverage[toml] >= 7.0 pytest pytest-snapshot beautifulsoup4 +sphinx[test] # needed for pinned version of defusedxml tests/issue_134/sphinx-immaterial-pybind11-issue-134 -r ../requirements.txt diff --git a/tests/system_fonts_test.py b/tests/system_fonts_test.py index 067866417..00d2b97b4 100644 --- a/tests/system_fonts_test.py +++ b/tests/system_fonts_test.py @@ -33,4 +33,4 @@ def test_system_font(immaterial_make_app): ) app.build() - assert not app._warning.getvalue() + assert not app._warning.getvalue() # type: ignore[attr-defined]