|
1 | 1 | from collections.abc import Iterator # only in python 3.11 afterwards |
2 | 2 | import contextlib |
| 3 | +from inspect import signature |
3 | 4 | from pathlib import Path |
4 | 5 | from timeit import default_timer as timer # Used for timing measurements |
5 | 6 | import tomllib |
|
35 | 36 |
|
36 | 37 | logger = logging.getLogger(__name__) |
37 | 38 |
|
| 39 | +try: |
| 40 | + from sphinx_needs.api import add_field as _add_field |
| 41 | +except ImportError: # sphinx-needs < 8 has no add_field |
| 42 | + _add_field = None |
| 43 | + |
| 44 | +# add_extra_option only accepts a schema on sphinx-needs >= 6 (where schema |
| 45 | +# validation exists); older versions take just (app, name). Probe the signature |
| 46 | +# instead of comparing versions, so no ``packaging`` dependency is needed. |
| 47 | +_USE_FIELD_SCHEMA = "schema" in signature(add_extra_option).parameters |
| 48 | + |
| 49 | + |
| 50 | +def _register_sn_field(app: Sphinx, name: str, description: str) -> None: |
| 51 | + """Register a string field, preferring the modern ``add_field`` API. |
| 52 | +
|
| 53 | + ``add_field`` (sphinx-needs >= 8) registers a typed field; older versions |
| 54 | + fall back to ``add_extra_option`` (only deprecated on >= 8). A typed field |
| 55 | + defaults to ``None`` and is stripped before schema validation, whereas an |
| 56 | + untyped field defaults to ``""`` and would trip a strict |
| 57 | + ``unevaluatedProperties: false`` schema on needs that never set it. |
| 58 | + """ |
| 59 | + schema = {"type": "string"} |
| 60 | + if _add_field is not None: |
| 61 | + _add_field(name, description, schema=schema) |
| 62 | + elif _USE_FIELD_SCHEMA: |
| 63 | + add_extra_option(app, name, schema=schema) |
| 64 | + else: |
| 65 | + add_extra_option(app, name) |
| 66 | + |
38 | 67 |
|
39 | 68 | def _check_sphinx_needs_dependency(app: Sphinx) -> bool: |
40 | 69 | """Check if sphinx-needs is actually loaded as an extension.""" |
@@ -185,13 +214,17 @@ def set_config_to_sphinx( |
185 | 214 |
|
186 | 215 | def update_sn_extra_options(app: Sphinx, config: _SphinxConfig) -> None: |
187 | 216 | src_trace_sphinx_config = CodeLinksConfig.from_sphinx(config) |
188 | | - add_extra_option(app, "project") |
189 | | - add_extra_option(app, "file") |
190 | | - add_extra_option(app, "directory") |
| 217 | + _register_sn_field(app, "project", "Source-tracing project") |
| 218 | + _register_sn_field(app, "file", "Source file") |
| 219 | + _register_sn_field(app, "directory", "Source directory") |
191 | 220 | if src_trace_sphinx_config.set_local_url: |
192 | | - add_extra_option(app, src_trace_sphinx_config.local_url_field) |
| 221 | + _register_sn_field( |
| 222 | + app, src_trace_sphinx_config.local_url_field, "Local source URL" |
| 223 | + ) |
193 | 224 | if src_trace_sphinx_config.set_remote_url: |
194 | | - add_extra_option(app, src_trace_sphinx_config.remote_url_field) |
| 225 | + _register_sn_field( |
| 226 | + app, src_trace_sphinx_config.remote_url_field, "Remote source URL" |
| 227 | + ) |
195 | 228 |
|
196 | 229 |
|
197 | 230 | def update_sn_types(app: Sphinx, _config: _SphinxConfig) -> None: |
|
0 commit comments