From b3d4f722ee8b3cb15bc2b201c7eb884274a9d30a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 10:50:13 +1000 Subject: [PATCH 01/10] Add a bit of vertical spacing around inheritance diagram --- _static/css/custom.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/_static/css/custom.css b/_static/css/custom.css index 3bfde70068d1a..e70233c5fe1db 100644 --- a/_static/css/custom.css +++ b/_static/css/custom.css @@ -1,3 +1,8 @@ +.graphviz { + margin-top: 10px; + margin-bottom: 10px; +} + /* adds scrollbar to sidenav */ .wy-side-scroll { width: auto; From ba3aabf20047384da45aec3c203bae10912bc99f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 10:51:56 +1000 Subject: [PATCH 02/10] Don't include single class inheritence diagrams --- scripts/make_api_rst.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index 87672849d907a..f821359473381 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -178,7 +178,7 @@ def _recursive_substitute(self, **kws): """ -class_header = """ +inheritance_diagram = """ .. inheritance-diagram:: qgis.$PACKAGE.$CLASS :parts: 1 """ @@ -318,10 +318,7 @@ def generate_docs(): header = "" toc = "" - if inspect.isclass(_class): - header = class_header - toc = class_toc - + bases_and_subclass_header = "" if hasattr(_class, "__bases__") and _class.__bases__: def export_bases(_b): @@ -342,22 +339,28 @@ def export_bases(_b): base_header = export_bases(_class) if base_header: - header += "\n" + write_header("Base classes") - header += f"\n+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[0]}+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[1]}+\n" - header += base_header + bases_and_subclass_header += "\n" + write_header("Base classes") + bases_and_subclass_header += f"\n+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[0]}+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[1]}+\n" + bases_and_subclass_header += base_header if hasattr(_class, "__subclasses__") and _class.__subclasses__(): - header += "\n" + write_header("Subclasses") - header += f"\n+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[0]}+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[1]}+\n" + bases_and_subclass_header += "\n" + write_header("Subclasses") + bases_and_subclass_header += f"\n+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[0]}+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[1]}+\n" for subclass in _class.__subclasses__(): - header += make_table_row( + bases_and_subclass_header += make_table_row( [ f"`{subclass.__name__} <{subclass.__name__}.html>`_", extract_summary(subclass.__doc__), ] ) + if inspect.isclass(_class): + if bases_and_subclass_header: + header += inheritance_diagram + header += bases_and_subclass_header + toc = class_toc + for method in dir(_class): if not hasattr(_class, method): continue From 5530aa595070b044fe5c48d695aad9102ec832ec Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 10:53:24 +1000 Subject: [PATCH 03/10] Fix incorrect bleeding of eg warning blocks over method table --- rst/qgis_pydoc_template.txt | 4 ++-- scripts/make_api_rst.py | 46 ++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/rst/qgis_pydoc_template.txt b/rst/qgis_pydoc_template.txt index 66423a0f51459..aea580c0d5fd6 100644 --- a/rst/qgis_pydoc_template.txt +++ b/rst/qgis_pydoc_template.txt @@ -7,10 +7,10 @@ Class: $CLASS $HEADER_CONTENT +$TABLE_OF_CONTENTS + .. autoclass:: qgis.$PACKAGE.$CLASS :special-members: __init__ :members: :undoc-members: :exclude-members: $EXCLUDE_METHODS - - $TABLE_OF_CONTENTS diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index f821359473381..1fb34b377c84d 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -184,29 +184,29 @@ def _recursive_substitute(self, **kws): """ class_toc = """ - .. autoautosummary:: qgis.$PACKAGE.$CLASS - :enums: - :nosignatures: - :exclude-members: $EXCLUDE_METHODS - - .. autoautosummary:: qgis.$PACKAGE.$CLASS - :methods: - :nosignatures: - :exclude-members: $EXCLUDE_METHODS - - .. autoautosummary:: qgis.$PACKAGE.$CLASS - :static_methods: - :nosignatures: - :exclude-members: $EXCLUDE_METHODS - - .. autoautosummary:: qgis.$PACKAGE.$CLASS - :signals: - :nosignatures: - :exclude-members: $EXCLUDE_METHODS - - .. autoautosummary:: qgis.$PACKAGE.$CLASS - :attributes: - :exclude-members: $EXCLUDE_METHODS +.. autoautosummary:: qgis.$PACKAGE.$CLASS + :enums: + :nosignatures: + :exclude-members: $EXCLUDE_METHODS + +.. autoautosummary:: qgis.$PACKAGE.$CLASS + :methods: + :nosignatures: + :exclude-members: $EXCLUDE_METHODS + +.. autoautosummary:: qgis.$PACKAGE.$CLASS + :static_methods: + :nosignatures: + :exclude-members: $EXCLUDE_METHODS + +.. autoautosummary:: qgis.$PACKAGE.$CLASS + :signals: + :nosignatures: + :exclude-members: $EXCLUDE_METHODS + +.. autoautosummary:: qgis.$PACKAGE.$CLASS + :attributes: + :exclude-members: $EXCLUDE_METHODS """ MODULE_TOC_MAX_COLUMN_SIZES = [300, 500] From 0d6a295f32b736e7942dd040ba9bf0e618ca1fb2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 11:16:04 +1000 Subject: [PATCH 04/10] Hacky approach to split docstring from constructors Since SIP appends the constructor docs to the end of the class docstring, use a hacky approach to re-split these off. This allows us to put the actual class documentation at the top of the page, leaving the constructor/__init__ docs to sit nicely just under the method listings. --- process_links.py | 15 ++++++++++++++- scripts/make_api_rst.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/process_links.py b/process_links.py index b7f79ebe3d65b..29f42b6a2a399 100644 --- a/process_links.py +++ b/process_links.py @@ -58,7 +58,20 @@ def create_links(doc: str) -> str: def process_docstring(app, what, name, obj, options, lines): - # print('d', what, name, obj, options) + if what == "class": + # hacky approach to detect nested classes, eg QgsCallout.QgsCalloutContext + is_nested = len(name.split(".")) > 3 + if not is_nested: + # remove docstring part, we've already included it in the page header + # only leave the __init__ methods + init_idx = 0 + class_name = name.split(".")[-1] + for init_idx, line in enumerate(lines): + if re.match(rf"^{class_name}\(", line): + break + + lines[:] = lines[init_idx:] + for i in range(len(lines)): # fix seealso diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index 1fb34b377c84d..144f389a0e23a 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -356,6 +356,21 @@ def export_bases(_b): ) if inspect.isclass(_class): + class_doc = _class.__doc__ + # only keep the actual class doc string part. SIP will + # append the constructor signatures and docs at the end + # of the class doc, so let's trim those off. + # They'll get included later in the actual listing of + # class methods + lines = class_doc.split("\n") + init_idx = 0 + for init_idx, line in enumerate(lines): + if re.match(rf"^{_class.__name__}\(", line): + break + + doc = "\n".join(lines[:init_idx]) + _class.__doc__ = doc + header = _class.__doc__ if bases_and_subclass_header: header += inheritance_diagram header += bases_and_subclass_header From 6005a23216f17dd4b6b5b33eac3bcf31f1eda3e7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 11:47:33 +1000 Subject: [PATCH 05/10] Handle missing class docstrings gracefully --- scripts/make_api_rst.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index 144f389a0e23a..cb7197bd290f4 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -362,16 +362,18 @@ def export_bases(_b): # of the class doc, so let's trim those off. # They'll get included later in the actual listing of # class methods - lines = class_doc.split("\n") - init_idx = 0 - for init_idx, line in enumerate(lines): - if re.match(rf"^{_class.__name__}\(", line): - break - - doc = "\n".join(lines[:init_idx]) - _class.__doc__ = doc - header = _class.__doc__ + if class_doc: + lines = class_doc.split("\n") + init_idx = 0 + for init_idx, line in enumerate(lines): + if re.match(rf"^{_class.__name__}\(", line): + break + + header = "\n".join(lines[:init_idx]) + if bases_and_subclass_header: + if header: + header += "\n" header += inheritance_diagram header += bases_and_subclass_header toc = class_toc From 044b45cbc46b39915a61803ace96198b555aafb5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 11:47:51 +1000 Subject: [PATCH 06/10] Guard against infinite recursion --- scripts/make_api_rst.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index cb7197bd290f4..1d9d8b34ae32d 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -100,7 +100,10 @@ def _recursive_substitute(self, **kws): raise ValueError("Max recursion depth exceeded") self.depth += 1 - result = super().safe_substitute(**kws) + try: + result = super().safe_substitute(**kws) + except RecursionError: + return self.template if "$" in result: return self.__class__(result)._recursive_substitute(**kws) From 27868e54e3e73c1c25b4fbce8413d4614355fcc7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 11:49:07 +1000 Subject: [PATCH 07/10] Ensure constructors are nicely presented --- process_links.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/process_links.py b/process_links.py index 29f42b6a2a399..830c073692b75 100644 --- a/process_links.py +++ b/process_links.py @@ -71,6 +71,20 @@ def process_docstring(app, what, name, obj, options, lines): break lines[:] = lines[init_idx:] + lines_out = [] + # loop through remaining lines, which are the constructors. Format + # these up so they look like proper __init__ method documentation + for i, line in enumerate(lines): + if re.match(rf"^{class_name}\(", line): + lines_out.append( + re.sub(rf"\b{class_name}\(", ".. py:method:: __init__(", line) + ) + lines_out.append("") + else: + lines_out.append(" " + line) + + lines[:] = lines_out[:] + return for i in range(len(lines)): From 60cc9f8f4b4075508cb4816abf43847657593d00 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 12:03:16 +1000 Subject: [PATCH 08/10] Add a heading before class hierarchy content --- scripts/make_api_rst.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index 1d9d8b34ae32d..8db8a032c72c8 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -342,12 +342,12 @@ def export_bases(_b): base_header = export_bases(_class) if base_header: - bases_and_subclass_header += "\n" + write_header("Base classes") + bases_and_subclass_header += "\n" + write_header("Base classes", 2) bases_and_subclass_header += f"\n+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[0]}+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[1]}+\n" bases_and_subclass_header += base_header if hasattr(_class, "__subclasses__") and _class.__subclasses__(): - bases_and_subclass_header += "\n" + write_header("Subclasses") + bases_and_subclass_header += "\n" + write_header("Subclasses", 2) bases_and_subclass_header += f"\n+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[0]}+{'-' * MODULE_TOC_MAX_COLUMN_SIZES[1]}+\n" for subclass in _class.__subclasses__(): @@ -377,6 +377,7 @@ def export_bases(_b): if bases_and_subclass_header: if header: header += "\n" + header += write_header("Class Hierarchy") header += inheritance_diagram header += bases_and_subclass_header toc = class_toc From e26a73cddba08f7cd79b47dc63d85fb3aefd51bc Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 14:41:04 +1000 Subject: [PATCH 09/10] Fix warnings --- scripts/make_api_rst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make_api_rst.py b/scripts/make_api_rst.py index 8db8a032c72c8..659172425fc50 100755 --- a/scripts/make_api_rst.py +++ b/scripts/make_api_rst.py @@ -376,7 +376,7 @@ def export_bases(_b): if bases_and_subclass_header: if header: - header += "\n" + header += "\n\n" header += write_header("Class Hierarchy") header += inheritance_diagram header += bases_and_subclass_header From fbffe0db725153b2b6e77b806cad1cc1e6fc3721 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Aug 2024 14:41:40 +1000 Subject: [PATCH 10/10] Don't try to index __init__ methods These aren't linked anywhere, and cause sphinx warnings because we have multiple __init__ methods for MANY classes --- process_links.py | 1 + 1 file changed, 1 insertion(+) diff --git a/process_links.py b/process_links.py index 830c073692b75..0a6243e5d0894 100644 --- a/process_links.py +++ b/process_links.py @@ -79,6 +79,7 @@ def process_docstring(app, what, name, obj, options, lines): lines_out.append( re.sub(rf"\b{class_name}\(", ".. py:method:: __init__(", line) ) + lines_out.append(" :noindex:") lines_out.append("") else: lines_out.append(" " + line)