sphinx-vtk-xref 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,233 @@
1
+ """Link to VTK's documentation with the ``:vtk:`` role."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from http import HTTPStatus
6
+ from typing import TYPE_CHECKING
7
+
8
+ from bs4 import BeautifulSoup
9
+ from docutils import nodes
10
+ import requests
11
+ from sphinx.util.docutils import ReferenceRole
12
+ from sphinx.util import logging
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ if TYPE_CHECKING:
17
+ from typing import ClassVar
18
+
19
+ #: Timeout (in seconds) for HTTP requests to the VTK documentation server.
20
+ HTTP_TIMEOUT = 30
21
+
22
+ #: HTTP status codes that, by default, do not fail the build. These typically
23
+ #: indicate a transient server-side issue (rate limiting or upstream
24
+ #: unavailability) rather than a genuinely-invalid class reference.
25
+ DEFAULT_IGNORED_STATUS_CODES = frozenset(
26
+ {
27
+ HTTPStatus.TOO_MANY_REQUESTS, # 429
28
+ HTTPStatus.INTERNAL_SERVER_ERROR, # 500
29
+ HTTPStatus.BAD_GATEWAY, # 502
30
+ HTTPStatus.SERVICE_UNAVAILABLE, # 503
31
+ HTTPStatus.GATEWAY_TIMEOUT, # 504
32
+ }
33
+ )
34
+
35
+
36
+ class VTKRole(ReferenceRole):
37
+ """Link to vtk class documentation using a custom role.
38
+
39
+ E.g. use :vtk:`vtkPolyData` for linking to the `vtkPolyData` class docs.
40
+ """
41
+
42
+ # Cache for (class, member) keys with urls as values
43
+ resolved_urls: ClassVar[dict[tuple[str, str | None], str]] = {}
44
+
45
+ def run(self): # numpydoc ignore=RT01
46
+ """Run the :vtk: role."""
47
+ INVALID_URL = "" # URL is set to empty string if not valid
48
+
49
+ cls_full = self.target
50
+ title = self.title
51
+
52
+ # Handle `~` prefix to shorten the title
53
+ if cls_full.startswith("~"):
54
+ cls_full = cls_full[1:]
55
+ if not self.has_explicit_title:
56
+ title = cls_full.split(".")[-1]
57
+
58
+ # Validate and split input like 'vtkClass.member'
59
+ parts = cls_full.split(".")
60
+ if len(parts) > 2:
61
+ cls_name = parts[0]
62
+ member_name = parts[1]
63
+ extra = ".".join(parts[2:])
64
+ self._warn_nested_members_ref(cls_name, member_name, extra)
65
+ else:
66
+ cls_name, member_name = parts[0], parts[1] if len(parts) == 2 else None
67
+ cls_url = _vtk_class_url(cls_name)
68
+
69
+ if not self._nitpicky():
70
+ # Link checking disabled: skip the HTTP validation/anchor lookup
71
+ # entirely and point straight at the (unvalidated) class URL.
72
+ node = nodes.reference(title, title, refuri=cls_url)
73
+ return [node], []
74
+
75
+ cache_key = (cls_name, member_name)
76
+ cached_url = self.resolved_urls.get(cache_key)
77
+ if cached_url is not None:
78
+ # Cache hit, check if valid or not
79
+ if cached_url == INVALID_URL:
80
+ # Not valid, report the error source
81
+ has_valid_class_url = self.resolved_urls.get((cls_name, None))
82
+ if member_name and has_valid_class_url:
83
+ # Class is valid but member is not
84
+ self._warn_invalid_class_member_ref(cls_name, member_name)
85
+ else:
86
+ self._warn_invalid_class_ref(cls_name)
87
+
88
+ # Use class URL fallback for invalid member anchor
89
+ refuri = cls_url
90
+ else:
91
+ # Cached url is valid
92
+ refuri = cached_url
93
+
94
+ node = nodes.reference(title, title, refuri=refuri)
95
+ return [node], []
96
+
97
+ # Not cached, build URL and validate
98
+ status_code: int | None = None
99
+ status_reason = ""
100
+ try:
101
+ response = requests.get(cls_url, timeout=HTTP_TIMEOUT)
102
+ status_code = response.status_code
103
+ status_reason = response.reason or ""
104
+ if status_code != HTTPStatus.OK:
105
+ msg = f"HTTP {status_code} {status_reason}".strip()
106
+ raise requests.RequestException(msg)
107
+ html = response.text
108
+ except requests.RequestException as exc:
109
+ if status_code is not None and status_code in self._ignored_status_codes():
110
+ # Transient server issue — do not fail the build. Emit an info
111
+ # message and fall back to the (unvalidated) class URL.
112
+ self._info_ignored_class_ref(cls_name, status_code, status_reason)
113
+ self.resolved_urls[cache_key] = cls_url
114
+ if member_name:
115
+ self.resolved_urls[(cls_name, None)] = cls_url
116
+ node = nodes.reference(title, title, refuri=cls_url)
117
+ return [node], []
118
+
119
+ # Invalid class url
120
+ reason = str(exc) if str(exc) else exc.__class__.__name__
121
+ self._warn_invalid_class_ref(cls_name, reason=reason)
122
+
123
+ # Create cache entries
124
+ self.resolved_urls[cache_key] = INVALID_URL
125
+ if member_name:
126
+ self.resolved_urls[(cls_name, None)] = INVALID_URL
127
+
128
+ # We return the reference even though the URL is bad
129
+ node = nodes.reference(title, title, refuri=cls_url)
130
+ return [node], []
131
+
132
+ if member_name:
133
+ anchor = _find_member_anchor(html, member_name)
134
+ if anchor:
135
+ full_url = f"{cls_url}#{anchor}"
136
+ self.resolved_urls[cache_key] = full_url
137
+ node = nodes.reference(title, title, refuri=full_url)
138
+ return [node], []
139
+ else:
140
+ # Anchor not found, mark cache as invalid but still fallback to class URL
141
+ self.resolved_urls[cache_key] = INVALID_URL
142
+ self._warn_invalid_class_member_ref(cls_name, member_name)
143
+
144
+ node = nodes.reference(title, title, refuri=cls_url)
145
+ return [node], []
146
+
147
+ # No member, just class URL
148
+ self.resolved_urls[cache_key] = cls_url
149
+ node = nodes.reference(title, title, refuri=cls_url)
150
+ return [node], []
151
+
152
+ def _ignored_status_codes(self):
153
+ try:
154
+ codes = self.env.config.sphinx_vtk_xref_ignored_status_codes
155
+ except AttributeError:
156
+ return DEFAULT_IGNORED_STATUS_CODES
157
+ return frozenset(codes)
158
+
159
+ def _nitpicky(self):
160
+ try:
161
+ return bool(self.env.config.sphinx_vtk_xref_nitpicky)
162
+ except AttributeError:
163
+ return True
164
+
165
+ def _warn_invalid_class_ref(self, cls_name, reason=None):
166
+ suffix = f" ({reason})" if reason else ""
167
+ self._issue_warning(
168
+ f"Invalid VTK class reference: '{cls_name}' → {_vtk_class_url(cls_name)}{suffix}"
169
+ )
170
+
171
+ def _warn_invalid_class_member_ref(self, cls_name, member_name):
172
+ self._issue_warning(
173
+ f"VTK method anchor not found for: '{cls_name}.{member_name}' → {_vtk_class_url(cls_name)}#<anchor>, "
174
+ f"the class URL is used instead."
175
+ )
176
+
177
+ def _warn_nested_members_ref(self, cls_name, member_name, extra):
178
+ self._issue_warning(
179
+ f"Too many nested members in VTK reference: '{cls_name}.{member_name}.{extra}'. "
180
+ f"Interpreting as '{cls_name}.{member_name}', ignoring: '{extra}'"
181
+ )
182
+
183
+ def _info_ignored_class_ref(self, cls_name, status_code, reason):
184
+ logger.info(
185
+ f"Ignoring HTTP {status_code} {reason} for VTK class reference: "
186
+ f"'{cls_name}' → {_vtk_class_url(cls_name)}",
187
+ location=self.get_location(),
188
+ type="sphinx-vtk-xref",
189
+ )
190
+
191
+ def _issue_warning(self, msg):
192
+ logger.warning(
193
+ msg,
194
+ location=self.get_location(),
195
+ type="sphinx-vtk-xref",
196
+ )
197
+
198
+
199
+ def _vtk_class_url(cls_name):
200
+ """Return the URL to the documentation for a VTK class."""
201
+ return f"https://vtk.org/doc/nightly/html/class{cls_name}.html"
202
+
203
+
204
+ def _find_member_anchor(html: str, member_name: str) -> str | None:
205
+ """Try to find the anchor ID for a method/attribute in the HTML."""
206
+ soup = BeautifulSoup(html, "html.parser")
207
+ headers = soup.find_all(["h2", "h3"], class_="memtitle")
208
+ for header in headers:
209
+ if member_name in header.get_text():
210
+ anchor = header.find_previous("a", id=True)
211
+ if anchor:
212
+ return anchor["id"]
213
+ return None
214
+
215
+
216
+ def setup(app):
217
+ app.add_role("vtk", VTKRole())
218
+ app.add_config_value(
219
+ "sphinx_vtk_xref_ignored_status_codes",
220
+ DEFAULT_IGNORED_STATUS_CODES,
221
+ "env",
222
+ types=(frozenset, set, list, tuple),
223
+ )
224
+ app.add_config_value(
225
+ "sphinx_vtk_xref_nitpicky",
226
+ True,
227
+ "env",
228
+ types=(bool,),
229
+ )
230
+ return {
231
+ "parallel_read_safe": True,
232
+ "parallel_write_safe": True,
233
+ }
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: sphinx-vtk-xref
3
+ Version: 0.2.1
4
+ Summary: Sphinx extension for linking to VTK class documentation.
5
+ Author-email: PyVista Developers <info@pyvista.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pyvista/sphinx-vtk-xref
8
+ Classifier: Framework :: Sphinx :: Extension
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/x-rst
12
+ License-File: LICENSE
13
+ Requires-Dist: sphinx>=8.1
14
+ Requires-Dist: beautifulsoup4
15
+ Requires-Dist: requests
16
+ Dynamic: license-file
17
+
18
+ Sphinx VTK XRef
19
+ ===============
20
+
21
+ ``sphinx-vtk-xref`` is a Sphinx extension for linking directly to
22
+ `VTK's documentation <https://vtk.org/doc/nightly/html/index.html>`_
23
+ using the ``:vtk:`` reference role.
24
+
25
+ Installation
26
+ ------------
27
+
28
+ #. Add ``sphinx-vtk-xref`` as a project dependency or install it with:
29
+
30
+ .. code-block:: bash
31
+
32
+ pip install sphinx-vtk-xref
33
+
34
+ #. Add ``sphinx_vtk_xref`` as an extension in your ``conf.py`` file used by
35
+ Sphinx. The exact setup depends on whether your documentation is written
36
+ in reStructuredText or Markdown.
37
+
38
+ reStructuredText
39
+ ~~~~~~~~~~~~~~~~
40
+
41
+ .. code-block:: python
42
+
43
+ extensions = [
44
+ ...,
45
+ 'sphinx_vtk_xref',
46
+ ]
47
+
48
+ Markdown (MyST)
49
+ ~~~~~~~~~~~~~~~
50
+
51
+ Markdown support requires `MyST-Parser <https://myst-parser.readthedocs.io>`_,
52
+ which dispatches Sphinx roles like ``:vtk:`` using its own ``{vtk}`` syntax.
53
+
54
+ .. code-block:: bash
55
+
56
+ pip install myst-parser
57
+
58
+ .. code-block:: python
59
+
60
+ extensions = [
61
+ ...,
62
+ 'sphinx_vtk_xref',
63
+ 'myst_parser',
64
+ ]
65
+ source_suffix = {
66
+ '.md': 'markdown',
67
+ }
68
+
69
+ Usage
70
+ -----
71
+
72
+ - Add links to VTK class documentation with the ``:vtk:`` role. For
73
+ example, write ``:vtk:`vtkImageData``` in docstrings to link directly
74
+ to the ``vtkImageData`` documentation. This will render as
75
+ `vtkImageData <https://vtk.org/doc/nightly/html/classvtkImageData.html>`_.
76
+
77
+ If using MyST, use ``{vtk}`vtkImageData``` instead.
78
+
79
+ - Link directly to class members such as methods or enums. For example,
80
+ write ``:vtk:`vtkImageData.GetSpacing``` to link directly to the
81
+ ``GetSpacing`` method. This will render as
82
+ `vtkImageData.GetSpacing <https://vtk.org/doc/nightly/html/classvtkImageData.html#ae6ebee83577b2d58c393a0df2f15b67d>`_.
83
+
84
+ If using MyST, use ``{vtk}`vtkImageData.GetSpacing``` instead.
85
+
86
+ - Use ``~`` to shorten the title for the link and only show the class member
87
+ after the period. For example, ``:vtk:`~vtkImageData.GetSpacing```
88
+ will render as
89
+ `GetSpacing <https://vtk.org/doc/nightly/html/classvtkImageData.html#ae6ebee83577b2d58c393a0df2f15b67d>`_.
90
+
91
+ If using MyST, use ``{vtk}`~vtkImageData.GetSpacing``` instead.
92
+
93
+ - Provide a custom title for the reference. For example,
94
+ ``:vtk:`Get Image Spacing <vtkImageData.GetSpacing>```
95
+ will render as
96
+ `Get Image Spacing <https://vtk.org/doc/nightly/html/classvtkImageData.html#ae6ebee83577b2d58c393a0df2f15b67d>`_
97
+
98
+ If using MyST, use ``{vtk}`Get Image Spacing <vtkImageData.GetSpacing>```
99
+ instead.
100
+
101
+ Configuration
102
+ -------------
103
+
104
+ The following options can be set in ``conf.py``:
105
+
106
+ ``sphinx_vtk_xref_nitpicky``
107
+ Bool, default ``True``. Set to ``False`` to disable ``:vtk:`` link
108
+ checking. This is independent of Sphinx's own ``nitpicky`` option, so
109
+ you can turn off ``:vtk:`` link validation without affecting how the rest
110
+ of your project handles missing references. When disabled, the ``:vtk:``
111
+ role skips the HTTP request used to validate class and member references
112
+ (and to resolve member anchors) and instead links directly to the
113
+ (unvalidated) class documentation page.
114
+
115
+ .. code-block:: python
116
+
117
+ sphinx_vtk_xref_nitpicky = False
118
+
119
+ ``sphinx_vtk_xref_ignored_status_codes``
120
+ Collection of HTTP status codes, default ``{429, 500, 502, 503, 504}``.
121
+ These codes typically indicate a transient server-side issue (rate
122
+ limiting or upstream unavailability) rather than a genuinely-invalid
123
+ class reference, so they are logged as info messages and do not fail the
124
+ build, even with Sphinx's ``-W`` flag. The role falls back to the
125
+ (unvalidated) class URL in this case.
126
+
127
+ .. code-block:: python
128
+
129
+ sphinx_vtk_xref_ignored_status_codes = {404}
130
+
131
+ Notes
132
+ -----
133
+
134
+ - The URLs linking to the VTK documentation are checked to ensure they are valid
135
+ references. A warning is emitted if the reference is invalid, but the role
136
+ will still try to point to a valid URL where possible. Combine this with
137
+ Sphinx's own ``-W`` flag to fail the build on invalid links.
138
+
139
+ - The role does not currently support linking to nested members. For example,
140
+ linking to an enum member with ``:vtk:`vtkCommand.EventIds``` works,
141
+ but linking to a specific enum value with ``:vtk:`vtkCommand.EventIds.PickEvent```
142
+ does not.
@@ -0,0 +1,6 @@
1
+ sphinx_vtk_xref/__init__.py,sha256=0tWteDJ6j6AXI0PmLZ9ViWaDzDVsJ_xilSFMQh_IpQQ,8561
2
+ sphinx_vtk_xref-0.2.1.dist-info/licenses/LICENSE,sha256=PzqiMqd_Z8IJxzsFO-wXfWdVZbvEUXC6_AzOC3-qReY,1064
3
+ sphinx_vtk_xref-0.2.1.dist-info/METADATA,sha256=bTPWRjo-I-yDqgj09FfYfFc_tSh74nhvMBu7A8Mdwuo,4753
4
+ sphinx_vtk_xref-0.2.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
5
+ sphinx_vtk_xref-0.2.1.dist-info/top_level.txt,sha256=QJkqut0INDWOZMlA9bNavrof8xZ9EGCY4GuBmVqdzL4,16
6
+ sphinx_vtk_xref-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 PyVista
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ sphinx_vtk_xref