From 5a3248050f98952ffc1bfc9517182eee95d371a4 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 19 Feb 2026 17:25:22 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20Expose=20`parse=5Fdynamic=5Ffunc?= =?UTF-8?q?tions`=20in=20field/link=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Mirrors the existing `parse_variants` exposure pattern to also expose `parse_dynamic_functions` as a user-configurable option on fields and links. Adds a new global config `needs_parse_dynamic_functions` (default `True`) to provide a phased migration path toward eventually defaulting to `False`. ## Motivation Previously, `parse_dynamic_functions` was hardcoded to `True` for all extra fields and links, with no way for users to disable it. This change gives users explicit control, and provides a single global knob to opt out of dynamic function parsing across the board. ## Changes **Config** (`sphinx_needs/config.py`): - Added `parse_dynamic_functions: bool | None` to `NewFieldParams`, `NeedFields`, `NeedLinksConfig`, and `_Config.add_field()` - Added `needs_parse_dynamic_functions` global config option (default `True`) to `NeedsSphinxConfig` **API** (`sphinx_needs/api/configuration.py`): - Added `parse_dynamic_functions` parameter to `add_field()` and `add_extra_option()` **Schema creation** (`sphinx_needs/needs.py`): - Extra fields and links now resolve `parse_dynamic_functions` from per-field config, falling back to the global `needs_parse_dynamic_functions` default (instead of hardcoded `True`) - Core field overrides pass `allow_dynamic_functions` to `create_inherited_field()` **Schema inheritance** (`sphinx_needs/needs_schema.py`): - `create_inherited_field()` now accepts and handles `allow_dynamic_functions`, gated by `allow_df` on core fields (same pattern as `parse_variants`/`allow_variants`) **Tests** (`tests/test_needs_schema.py`, `tests/test_dynamic_functions.py`): - Updated all existing `create_inherited_field` test calls with new required parameter - Added tests for `parse_dynamic_functions` override (allowed, rejected, invalid type) - Added integration test for `parse_dynamic_functions: false` on a field **Docs** (`docs/configuration.rst`, `docs/changelog.rst`): - Documented `parse_dynamic_functions` option under `needs_fields`, `needs_links`, and `needs_extra_links` - Added `needs_parse_dynamic_functions` config section with usage example - Added changelog entry ## Migration path | Phase | `needs_parse_dynamic_functions` default | Behavior | |-------|----------------------------------------|----------| | Now | `True` | Backward compatible; users can opt out per-field or globally | | Future major release | `False` | Dynamic function parsing requires explicit opt-in | --- docs/changelog.rst | 9 ++ docs/configuration.rst | 34 +++++ sphinx_needs/api/configuration.py | 6 + sphinx_needs/config.py | 23 +++ sphinx_needs/needs.py | 13 +- sphinx_needs/needs_schema.py | 17 ++- .../__snapshots__/test_dynamic_functions.ambr | 25 ++- tests/doc_test/doc_dynamic_functions/conf.py | 5 +- .../doc_test/doc_dynamic_functions/index.rst | 4 + tests/test_needs_schema.py | 142 +++++++++++++++--- 10 files changed, 249 insertions(+), 29 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4ad305897..1eb44b7a1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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 diff --git a/docs/configuration.rst b/docs/configuration.rst index bb1052dc6..5ab2a5e79 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -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 `. 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: @@ -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 `. 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: " incoming". - ``outgoing`` (optional): Outgoing text, to use for outgoing links. E.g. "blocks". Default: "". - ``copy`` (optional): True/False. If True, the links will be copied also to the common link-list (link type ``links``). @@ -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 ~~~~~~~~~~~~~~~~~~~~ @@ -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 `. 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``). diff --git a/sphinx_needs/api/configuration.py b/sphinx_needs/api/configuration.py index 69a6b8cba..600b36e3c 100644 --- a/sphinx_needs/api/configuration.py +++ b/sphinx_needs/api/configuration.py @@ -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``. @@ -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", @@ -130,6 +132,7 @@ def add_extra_option( schema=schema, nullable=nullable, parse_variants=parse_variants, + parse_dynamic_functions=parse_dynamic_functions, ) @@ -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. @@ -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, @@ -172,6 +177,7 @@ def add_field( default=default, predicates=predicates, parse_variants=parse_variants, + parse_dynamic_functions=parse_dynamic_functions, ) diff --git a/sphinx_needs/config.py b/sphinx_needs/config.py index 3d03108ec..f31fb406a 100644 --- a/sphinx_needs/config.py +++ b/sphinx_needs/config.py @@ -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. @@ -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.""" @@ -161,6 +164,7 @@ def add_field( default=default, predicates=predicates, parse_variants=parse_variants, + parse_dynamic_functions=parse_dynamic_functions, ) @property @@ -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): @@ -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): @@ -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,)} diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py index d6ec6dd59..ad4097cef 100644 --- a/sphinx_needs/needs.py +++ b/sphinx_needs/needs.py @@ -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, ) @@ -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( @@ -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, @@ -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, ) @@ -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, diff --git a/sphinx_needs/needs_schema.py b/sphinx_needs/needs_schema.py index d6433fb43..58a2bc2d3 100644 --- a/sphinx_needs/needs_schema.py +++ b/sphinx_needs/needs_schema.py @@ -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. @@ -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] = {} @@ -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) diff --git a/tests/__snapshots__/test_dynamic_functions.ambr b/tests/__snapshots__/test_dynamic_functions.ambr index e454e6d7c..114ea20b5 100644 --- a/tests/__snapshots__/test_dynamic_functions.ambr +++ b/tests/__snapshots__/test_dynamic_functions.ambr @@ -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#', @@ -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', diff --git a/tests/doc_test/doc_dynamic_functions/conf.py b/tests/doc_test/doc_dynamic_functions/conf.py index 45ea42a4a..2eb6dd223 100644 --- a/tests/doc_test/doc_dynamic_functions/conf.py +++ b/tests/doc_test/doc_dynamic_functions/conf.py @@ -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 diff --git a/tests/doc_test/doc_dynamic_functions/index.rst b/tests/doc_test/doc_dynamic_functions/index.rst index 8207bb1f6..aaf72e882 100644 --- a/tests/doc_test/doc_dynamic_functions/index.rst +++ b/tests/doc_test/doc_dynamic_functions/index.rst @@ -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)]] diff --git a/tests/test_needs_schema.py b/tests/test_needs_schema.py index 598c396fd..38d3cadd2 100644 --- a/tests/test_needs_schema.py +++ b/tests/test_needs_schema.py @@ -1462,14 +1462,18 @@ def test_nullable_cannot_widen(self): with pytest.raises( ValueError, match="Cannot change 'nullable' from False to True" ): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_nullable_can_narrow(self): """Test that nullable can be changed from True to False (narrowing).""" parent = self._base_field(nullable=True) child = {"nullable": False} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.nullable is False def test_nullable_false_on_already_non_nullable(self): @@ -1477,7 +1481,9 @@ def test_nullable_false_on_already_non_nullable(self): parent = self._base_field(nullable=False) child = {"nullable": False} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.nullable is False def test_nullable_true_on_already_nullable(self): @@ -1485,7 +1491,9 @@ def test_nullable_true_on_already_nullable(self): parent = self._base_field(nullable=True) child = {"nullable": True} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.nullable is True def test_parse_variants_not_allowed(self): @@ -1496,14 +1504,18 @@ def test_parse_variants_not_allowed(self): with pytest.raises( ValueError, match="parse_variants is not allowed to be True" ): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_parse_variants_allowed_when_permitted(self): """Test that parse_variants=True is allowed when permitted.""" parent = self._base_field() child = {"parse_variants": True} - result = create_inherited_field(parent, child, allow_variants=True) + result = create_inherited_field( + parent, child, allow_variants=True, allow_dynamic_functions=False + ) assert result.parse_variants is True def test_parse_variants_false_explicitly(self): @@ -1511,15 +1523,65 @@ def test_parse_variants_false_explicitly(self): parent = self._base_field() child = {"parse_variants": False} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.parse_variants is False + def test_parse_dynamic_functions_not_allowed(self): + """Test that parse_dynamic_functions=True is rejected when not allowed.""" + parent = self._base_field() + child = {"parse_dynamic_functions": True} + + with pytest.raises( + ValueError, + match="parse_dynamic_functions is not allowed to be True", + ): + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) + + def test_parse_dynamic_functions_allowed_when_permitted(self): + """Test that parse_dynamic_functions=True is allowed when permitted.""" + parent = self._base_field() + child = {"parse_dynamic_functions": True} + + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=True + ) + assert result.parse_dynamic_functions is True + + def test_parse_dynamic_functions_false_explicitly(self): + """Test that explicitly setting parse_dynamic_functions=False is allowed.""" + parent = self._base_field() + child = {"parse_dynamic_functions": False} + + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) + assert result.parse_dynamic_functions is False + + def test_parse_dynamic_functions_invalid_type(self): + """Test that non-boolean parse_dynamic_functions value raises error.""" + parent = self._base_field() + child = {"parse_dynamic_functions": "yes"} + + with pytest.raises( + ValueError, + match="Child 'parse_dynamic_functions' must be a boolean", + ): + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) + def test_description_override(self): """Test that child description overrides parent description.""" parent = self._base_field(description="Original description") child = {"description": "New description"} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.description == "New description" def test_description_inherited_when_not_provided(self): @@ -1527,7 +1589,9 @@ def test_description_inherited_when_not_provided(self): parent = self._base_field(description="Original description") child = {} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.description == "Original description" def test_empty_child_inherits_from_parent(self): @@ -1540,7 +1604,9 @@ def test_empty_child_inherits_from_parent(self): ) child = {} - result = create_inherited_field(parent, child, allow_variants=True) + result = create_inherited_field( + parent, child, allow_variants=True, allow_dynamic_functions=False + ) assert result.nullable is True assert result.parse_variants is True assert result.description == "Parent description" @@ -1554,14 +1620,18 @@ def test_schema_type_mismatch_at_field_level(self): with pytest.raises( ValueError, match=r"Child 'type'.*does not match parent 'type'" ): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_schema_inheritance_valid_subset(self): """Test valid schema inheritance with enum subset.""" parent = self._base_field(schema={"type": "string", "enum": ["a", "b", "c"]}) child = {"schema": {"type": "string", "enum": ["a", "b"]}} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema == {"type": "string", "enum": ["a", "b"]} def test_schema_inheritance_invalid_subset(self): @@ -1570,14 +1640,18 @@ def test_schema_inheritance_invalid_subset(self): child = {"schema": {"type": "string", "enum": ["a", "c"]}} with pytest.raises(ValueError, match="are not a subset"): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_child_adds_constraint_when_parent_has_none(self): """Test that child can add constraints when parent doesn't have them.""" parent = self._base_field(schema={"type": "string"}) child = {"schema": {"type": "string", "minLength": 5, "pattern": "^[a-z]+$"}} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema == { "type": "string", "minLength": 5, @@ -1591,7 +1665,9 @@ def test_combined_enum_narrowing_and_const(self): ) child = {"schema": {"type": "string", "enum": ["a", "b"], "const": "a"}} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema["enum"] == ["a", "b"] assert result.schema["const"] == "a" @@ -1601,7 +1677,9 @@ def test_invalid_nullable_type(self): child = {"nullable": "yes"} with pytest.raises(ValueError, match="Child 'nullable' must be a boolean"): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_invalid_parse_variants_type(self): """Test that non-boolean parse_variants value raises error.""" @@ -1611,7 +1689,9 @@ def test_invalid_parse_variants_type(self): with pytest.raises( ValueError, match="Child 'parse_variants' must be a boolean" ): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_invalid_description_type(self): """Test that non-string description value raises error.""" @@ -1619,7 +1699,9 @@ def test_invalid_description_type(self): child = {"description": 123} with pytest.raises(ValueError, match="Child 'description' must be a string"): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_array_item_type_mismatch(self): """Test type mismatch error for array item types.""" @@ -1627,7 +1709,9 @@ def test_array_item_type_mismatch(self): child = {"schema": {"type": "array", "items": {"type": "integer"}}} with pytest.raises(ValueError, match=r"'items' inheritance.*does not match"): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_array_item_constraints_inherited(self): """Test that array item constraints are properly inherited.""" @@ -1638,7 +1722,9 @@ def test_array_item_constraints_inherited(self): "schema": {"type": "array", "items": {"type": "string", "minLength": 5}} } - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema["items"]["minLength"] == 5 def test_array_item_constraint_violation(self): @@ -1651,7 +1737,9 @@ def test_array_item_constraint_violation(self): } with pytest.raises(ValueError, match=r"'minLength'.*is less than parent"): - create_inherited_field(parent, child, allow_variants=False) + create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) def test_multiple_properties_overridden(self): """Test that multiple properties can be overridden simultaneously.""" @@ -1666,7 +1754,9 @@ def test_multiple_properties_overridden(self): "description": "Updated", } - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema["minLength"] == 5 assert result.nullable is False assert result.description == "Updated" @@ -1684,7 +1774,9 @@ def test_uniqueitems_can_become_more_restrictive(self): } } - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema["uniqueItems"] is True def test_contains_inherited_when_not_overridden(self): @@ -1698,5 +1790,7 @@ def test_contains_inherited_when_not_overridden(self): ) child = {"schema": {"type": "array", "items": {"type": "string"}}} - result = create_inherited_field(parent, child, allow_variants=False) + result = create_inherited_field( + parent, child, allow_variants=False, allow_dynamic_functions=False + ) assert result.schema["contains"] == {"type": "string", "pattern": "^test"}