Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ Changelog
Also adds support for a ``description`` field for link types.
The old ``needs_extra_links`` configuration is now deprecated but remains supported for backward compatibility.

- ✨ Expose ``parse_dynamic_functions`` in field and link configuration

Add per-field/link ``parse_dynamic_functions`` option to :ref:`needs_fields`, :ref:`needs_links`,
and the ``add_field`` / ``add_extra_option`` API functions.
Also add a new :ref:`needs_parse_dynamic_functions` global config option (default ``True``)
that sets the default for all extra fields and links when not explicitly set per-field/link.
This provides a migration path to eventually disable dynamic function parsing by default
in a future major release.

.. _`release:6.2.0`:

6.2.0
Expand Down
34 changes: 34 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ For new fields the following can be defined:
If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>`.
Default: ``False``.
- ``parse_dynamic_functions``: If set to ``True``, the field will support :ref:`dynamic_functions`.
Default: the value of :ref:`needs_parse_dynamic_functions` (``True``).

For example:

Expand Down Expand Up @@ -378,6 +380,8 @@ Each configured link can define:
If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>`.
Default: ``False``.
- ``parse_dynamic_functions``: If set to ``True``, the field will support :ref:`dynamic_functions`.
Default: the value of :ref:`needs_parse_dynamic_functions` (``True``).
- ``incoming`` (optional): Incoming text, to use for incoming links. E.g. "is blocked by". Default: "<name> incoming".
- ``outgoing`` (optional): Outgoing text, to use for outgoing links. E.g. "blocks". Default: "<name>".
- ``copy`` (optional): True/False. If True, the links will be copied also to the common link-list (link type ``links``).
Expand Down Expand Up @@ -989,6 +993,34 @@ exceed the setting from :ref:`needs_id_length`.

.. _`needs_title_optional`:

.. _`needs_parse_dynamic_functions`:

needs_parse_dynamic_functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionadded:: 7.0.0

Sets the global default for whether :ref:`dynamic_functions` (``[[...]]``) are parsed in extra fields and link fields.

When ``True`` (the default), all extra fields and links will have dynamic function
parsing enabled unless explicitly set to ``False`` per-field/link via
:ref:`needs_fields` or :ref:`needs_links`.

.. code-block:: python

# Disable dynamic function parsing globally
needs_parse_dynamic_functions = False

# Then opt-in per field
needs_fields = {
"my_field": {
"parse_dynamic_functions": True,
},
}

By default this option is set to **True** for backward compatibility.
In a future major release, the default will change to ``False``.

needs_title_optional
~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -2772,6 +2804,8 @@ Each configured link should define:
If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>`.
Default: ``False``.
- ``parse_dynamic_functions``: If set to ``True``, the field will support :ref:`dynamic_functions`.
Default: the value of :ref:`needs_parse_dynamic_functions` (``True``).
- ``incoming`` (optional): Incoming text, to use for incoming links. E.g. "is blocked by".
- ``outgoing`` (optional): Outgoing text, to use for outgoing links. E.g. "blocks".
- ``copy`` (optional): True/False. If True, the links will be copied also to the common link-list (link type ``links``).
Expand Down
6 changes: 6 additions & 0 deletions sphinx_needs/api/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def add_extra_option(
schema: FieldSchemaTypes | None = None,
nullable: bool | None = None,
parse_variants: bool | None = None,
parse_dynamic_functions: bool | None = None,
) -> None:
"""
Adds an extra option to the configuration. This option can then later be used inside needs or ``add_need``.
Expand All @@ -117,6 +118,7 @@ def add_extra_option(
:param schema: Schema definition for the extra option
:param nullable: Whether the field allows unset values.
:param parse_variants: Whether variants are parsed in this field.
:param parse_dynamic_functions: Whether dynamic functions are parsed in this field.
"""
warnings.warn(
"add_extra_option is deprecated, use add_field instead",
Expand All @@ -130,6 +132,7 @@ def add_extra_option(
schema=schema,
nullable=nullable,
parse_variants=parse_variants,
parse_dynamic_functions=parse_dynamic_functions,
)


Expand All @@ -143,6 +146,7 @@ def add_field(
default: None | Any = None,
predicates: None | list[tuple[str, Any]] = None,
parse_variants: bool | None = None,
parse_dynamic_functions: bool | None = None,
) -> None:
"""
Adds an need field to the configured need schema.
Expand All @@ -162,6 +166,7 @@ def add_field(
:param default: Default value for the field, if not set in a need.
:param predicates: List of (need filter, value) pairs for default predicate values.
:param parse_variants: Whether variants are parsed in this field.
:param parse_dynamic_functions: Whether dynamic functions are parsed in this field.
"""
_NEEDS_CONFIG.add_field(
name,
Expand All @@ -172,6 +177,7 @@ def add_field(
default=default,
predicates=predicates,
parse_variants=parse_variants,
parse_dynamic_functions=parse_dynamic_functions,
)


Expand Down
23 changes: 23 additions & 0 deletions sphinx_needs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class NewFieldParams:
"""A JSON schema for the option."""
parse_variants: bool | None = None
"""Whether variants are parsed in this field."""
parse_dynamic_functions: bool | None = None
"""Whether dynamic functions are parsed in this field."""
predicates: None | list[tuple[str, Any]] = None
"""List of (need filter, value) pairs for default predicate values.

Expand Down Expand Up @@ -130,6 +132,7 @@ def add_field(
default: None | Any = None,
predicates: None | list[tuple[str, Any]] = None,
parse_variants: None | bool = None,
parse_dynamic_functions: None | bool = None,
override: bool = False,
) -> None:
"""Adds a need field to the configuration."""
Expand Down Expand Up @@ -161,6 +164,7 @@ def add_field(
default=default,
predicates=predicates,
parse_variants=parse_variants,
parse_dynamic_functions=parse_dynamic_functions,
)

@property
Expand Down Expand Up @@ -298,6 +302,8 @@ class NeedLinksConfig(TypedDict, total=False):
"""List of (need filter, value) pairs for predicate default values."""
parse_variants: NotRequired[bool]
"""Whether variants are parsed in this field."""
parse_dynamic_functions: NotRequired[bool]
"""Whether dynamic functions are parsed in this field."""


class LinkOptionsType(NeedLinksConfig):
Expand Down Expand Up @@ -342,6 +348,8 @@ class NeedFields(TypedDict):
"""List of (need filter, value) pairs for predicate default values."""
parse_variants: NotRequired[bool]
"""Whether variants are parsed in this field."""
parse_dynamic_functions: NotRequired[bool]
"""Whether dynamic functions are parsed in this field."""


class NeedField(NeedFields):
Expand Down Expand Up @@ -891,6 +899,21 @@ def warnings(
)
"""List of need fields that may contain variants."""

_parse_dynamic_functions: bool = field(
default=True, metadata={"rebuild": "html", "types": (bool,)}
)
"""Global default for whether dynamic functions (``[[...]]``) are parsed in extra fields and links.

When ``True`` (the default), all extra fields and links will have dynamic function
parsing enabled unless explicitly set to ``False`` per-field/link.

.. note::

This default is ``True`` for backward compatibility.
In a future major release, the default will change to ``False``,
requiring users to explicitly opt-in to dynamic function parsing per-field/link.
"""

# add render context option
render_context: dict[str, Any] = field(
default_factory=dict, metadata={"rebuild": "html", "types": (dict,)}
Expand Down
13 changes: 11 additions & 2 deletions sphinx_needs/needs.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ def load_config(app: Sphinx, *_args: Any) -> None:
default=option_params.get("default"),
predicates=option_params.get("predicates"),
parse_variants=option_params.get("parse_variants"),
parse_dynamic_functions=option_params.get("parse_dynamic_functions"),
override=True,
)

Expand Down Expand Up @@ -884,6 +885,7 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N
field,
core_override,
allow_variants=data.get("allow_variants", False),
allow_dynamic_functions=data.get("allow_df", False),
)
if "default" in core_override:
_set_default_on_field(
Expand Down Expand Up @@ -950,6 +952,11 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N
if name in needs_config._variant_options:
# for backward compatibility with deprecated config option
parse_variants = True
parse_dynamic_functions = (
needs_config._parse_dynamic_functions
if field_data.parse_dynamic_functions is None
else field_data.parse_dynamic_functions
)
field = FieldSchema(
name=name,
description=field_data.description,
Expand All @@ -962,7 +969,7 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N
else FieldLiteralValue(""),
allow_defaults=True,
allow_extend=True,
parse_dynamic_functions=True,
parse_dynamic_functions=parse_dynamic_functions,
parse_variants=parse_variants,
directive_option=True,
)
Expand Down Expand Up @@ -1021,7 +1028,9 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N
default=LinksLiteralValue([]),
allow_defaults=True,
allow_extend=True,
parse_dynamic_functions=True,
parse_dynamic_functions=link.get(
"parse_dynamic_functions", needs_config._parse_dynamic_functions
),
parse_variants=link.get("parse_variants", False),
directive_option=True,
display=display_config,
Expand Down
17 changes: 16 additions & 1 deletion sphinx_needs/needs_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,11 @@ def _split_string(


def create_inherited_field(
parent: FieldSchema, child: NeedFields, *, allow_variants: bool
parent: FieldSchema,
child: NeedFields,
*,
allow_variants: bool,
allow_dynamic_functions: bool,
) -> FieldSchema:
"""Create a new FieldSchema by inheriting from a parent FieldSchema and applying overrides from a child dictionary.

Expand All @@ -1042,6 +1046,8 @@ def create_inherited_field(
:param allow_variants: Whether to allow parse_variants to be set to True in the child.
This is a bit of a special case for certain core fields, and maybe should be handled differently in the future;
fields like ``template`` are used before variants are processed, so allowing variants there would not make sense.
:param allow_dynamic_functions: Whether to allow parse_dynamic_functions to be set to True in the child.
Core fields that do not support dynamic functions (``allow_df=False``) cannot have this overridden to True.
"""
replacements: dict[str, Any] = {}

Expand Down Expand Up @@ -1069,6 +1075,15 @@ def create_inherited_field(
raise ValueError("parse_variants is not allowed to be True for this field.")
replacements["parse_variants"] = child["parse_variants"]

if "parse_dynamic_functions" in child:
if not isinstance(child["parse_dynamic_functions"], bool):
raise ValueError("Child 'parse_dynamic_functions' must be a boolean.")
if not allow_dynamic_functions and child["parse_dynamic_functions"]:
raise ValueError(
"parse_dynamic_functions is not allowed to be True for this field."
)
replacements["parse_dynamic_functions"] = child["parse_dynamic_functions"]

return replace(parent, **replacements)


Expand Down
25 changes: 24 additions & 1 deletion tests/__snapshots__/test_dynamic_functions.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,22 @@
'type': 'spec',
'type_name': 'Specification',
}),
'TEST_9': dict({
'docname': 'index',
'external_css': 'external_link',
'id': 'TEST_9',
'lineno': 56,
'non_func': '[[test(need.unknown)]]',
'section_name': 'DYNAMIC FUNCTIONS',
'sections': list([
'DYNAMIC FUNCTIONS',
]),
'title': 'TEST_9',
'type': 'spec',
'type_name': 'Specification',
}),
}),
'needs_amount': 5,
'needs_amount': 6,
'needs_defaults_removed': True,
'needs_schema': dict({
'$schema': 'http://json-schema.org/draft-07/schema#',
Expand Down Expand Up @@ -367,6 +381,15 @@
'field_type': 'core',
'type': 'integer',
}),
'non_func': dict({
'default': None,
'description': 'Added by needs_fields config',
'field_type': 'extra',
'type': list([
'string',
'null',
]),
}),
'params': dict({
'default': None,
'description': 'Added by service open-needs',
Expand Down
5 changes: 4 additions & 1 deletion tests/doc_test/doc_dynamic_functions/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
},
]

needs_fields = {"test_func": {"nullable": True}}
needs_fields = {
"test_func": {"nullable": True},
"non_func": {"nullable": True, "parse_dynamic_functions": False},
}

needs_build_json = True
needs_json_remove_defaults = True
4 changes: 4 additions & 0 deletions tests/doc_test/doc_dynamic_functions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ These should warn since they have no associated need: :need_func:`[[copy("id")]]
.. spec:: TEST_8
:id: TEST_8
:test_func: [[test(need.unknown)]]

.. spec:: TEST_9
:id: TEST_9
:non_func: [[test(need.unknown)]]
Loading
Loading