Delayed field evaluation

Sphinx-needs offers the possibility to delay the evaluation of certain need field values until all needs have been collected, and thus can be based on this data.

There are two syntaxes provided to achieve this:

  • Using double square brackets [[...]] to encapsulate a dynamic function call.

  • Using double angled brackets <<...>> to encapsulate a variant definition.

Dynamic functions

Dynamic functions provide a mechanism to specify need fields or content that are calculated at build time, based on other fields or needs.

We do this by giving an author the possibility to set a function call to a predefined function, which calculates the final value after all needs have been collected.

For instance, you can use the feature if the status of a requirement depends on linked test cases and their status. Or if you will request specific data from an external server like JIRA.

To refer to a dynamic function, you can use the following syntax:

  • In a need directive option, wrap the function call in double square brackets: [[function_name(arg)]]

  • In a need content, use the ndf role: :ndf:`function_name(arg)`

Example 1: Dynamic function example

.. req:: my test requirement
   :id: df_1
   :status: open
   :tags: test;[[copy("status")]]

   This need has id :ndf:`copy("id")` and status :ndf:`copy("status")`.
Requirement: my test requirement df_1
status: open
tags: test, open

This need has id df_1 and status open.

Dynamic functions can be used for the following directive options:

Deprecated since version 3.1.0: The ndf role replaces the use of the [[...]] syntax in need content.

Built-in functions

The following functions are available by default.

Note

The parameters app, need and needs of the following functions are set automatically.

test

test(app: Sphinx, need: NeedItem | NeedPartItem | None, needs: NeedsMutable | NeedsView, *args: Any, **kwargs: Any) str

Test function for dynamic functions in sphinx needs.

Collects every given args and kwargs and returns a single string, which contains their values/keys.

Example 2

.. req:: test requirement

    :ndf:`test('arg_1', [1,2,3], my_keyword='awesome')`
Requirement: test requirement R_A6A4E
signature: test

Test output of dynamic function; need: R_A6A4E; args: ('arg_1', [1, 2, 3]); kwargs: {'my_keyword': 'awesome'}

Returns:

single test string

echo

echo(app: Sphinx, need: NeedItem | NeedPartItem | None, needs: NeedsMutable | NeedsView, text: str, *args: Any, **kwargs: Any) str

Added in version 0.6.3.

Just returns the given string back. Mostly useful for tests.

Example 3

A nice :ndf:`echo("first test")` for a dynamic function.

A nice first test for a dynamic function.

copy

copy(app: Sphinx, need: NeedItem | NeedPartItem | None, needs: NeedsMutable | NeedsView, option: str, need_id: str | None = None, lower: bool = False, upper: bool = False, filter: str | None = None) Any

Copies the value of one need option to another

Example 4

.. req:: copy-example
   :id: copy_1
   :tags: tag_1, tag_2, tag_3
   :status: open

.. spec:: copy-example implementation
   :id: copy_2
   :status: [[copy("status", "copy_1")]]
   :links: copy_1
   :comment: [[copy("id")]]

   Copies status of ``copy_1`` to own status.
   Sets also a comment, which copies the id of own need.

.. test:: test of specification and requirement
   :id: copy_3
   :links: copy_2; [[copy('links', 'copy_2')]]
   :tags: [[copy('tags', 'copy_1')]]

   Set own link to ``copy_2`` and also copies all links from it.

   Also copies all tags from copy_1.
Requirement: copy-example copy_1
status: open
tags: tag_1, tag_2, tag_3
signature: copy
links incoming: copy_2, copy_3
Specification: copy-example implementation copy_2
status: open
signature: copy
comment: copy_2
links outgoing: copy_1
links incoming: copy_3

Copies status of copy_1 to own status. Sets also a comment, which copies the id of own need.

Test Case: test of specification and requirement copy_3
tags: tag_1, tag_2, tag_3
signature: copy
links outgoing: copy_2, copy_1

Set own link to copy_2 and also copies all links from it.

Also copies all tags from copy_1.

If the filter_string needs to compare a value from the current need and the value is unknown yet, you can reference the valued field by using current_need["my_field"] inside the filter string. Small example:

.. test:: test of current_need value
   :id: copy_4

   The following copy command copies the title of the first need found under the same  highest
   section (headline):

   :ndf:`copy('title', filter='current_need["sections"][-1]==sections[-1]')`
Test Case: test of current_need value copy_4
signature: copy

The following copy command copies the title of the first need found under the same highest section (headline):

my test requirement

Parameters:
  • option – Name of the option to copy

  • need_id – id of the need, which contains the source option. If None, current need is taken

  • upper – Is set to True, copied value will be uppercase

  • lower – Is set to True, copied value will be lowercase

  • filterFilter string, which first result is used as copy source.

Returns:

string of copied need option

check_linked_values

check_linked_values(app: Sphinx, need: NeedItem | NeedPartItem | None, needs: NeedsMutable | NeedsView, result: Any, search_option: str, search_value: Any, filter_string: str | None = None, one_hit: bool = False) Any

Returns a specific value, if for all linked needs a given option has a given value.

The linked needs can be filtered by using the filter option.

If one_hit is set to True, only one linked need must have a positive match for the searched value.

Examples

Needs used as input data

Example 5

.. req:: Input A
   :id: clv_A
   :status: in progress

.. req:: Input B
   :id: clv_B
   :status: in progress

.. spec:: Input C
   :id: clv_C
   :status: closed
Requirement: Input A clv_A
status: in progress
signature: check_linked_values
links incoming: S_3A83D, S_4137F, S_78442, S_9ED38, S_951C8
Requirement: Input B clv_B
status: in progress
signature: check_linked_values
links incoming: S_3A83D, S_4137F, S_78442, S_9ED38, S_951C8
Specification: Input C clv_C
status: closed
signature: check_linked_values
links incoming: S_4137F, S_78442, S_9ED38, S_951C8

Example 1: Positive check

Status gets set to progress.

Example 6

.. spec:: result 1: Positive check
   :links: clv_A, clv_B
   :status: [[check_linked_values('progress', 'status', 'in progress' )]]
   :collapse: False
Specification: result 1: Positive check S_3A83D
status: progress
signature: check_linked_values
links outgoing: clv_A, clv_B

Example 2: Negative check

Status gets not set to progress, because status of linked need clv_C does not match “in progress”.

Example 7

.. spec:: result 2: Negative check
   :links: clv_A, clv_B, clv_C
   :status: [[check_linked_values('progress', 'status', 'in progress' )]]
   :collapse: False
Specification: result 2: Negative check S_4137F
status: None
signature: check_linked_values
links outgoing: clv_A, clv_B, clv_C

Example 3: Positive check thanks of used filter

status gets set to progress, because linked need clv_C is not part of the filter.

Example 8

.. spec:: result 3: Positive check thanks of used filter
   :links: clv_A, clv_B, clv_C
   :status: [[check_linked_values('progress', 'status', 'in progress', 'type == "req" ' )]]
   :collapse: False
Specification: result 3: Positive check thanks of used filter S_78442
status: progress
signature: check_linked_values
links outgoing: clv_A, clv_B, clv_C

Example 4: Positive check thanks of one_hit option

Even clv_C has not the searched status, status gets anyway set to progress. That’s because one_hit is used so that only one linked need must have the searched value.

Example 9

.. spec:: result 4: Positive check thanks of one_hit option
   :links: clv_A, clv_B, clv_C
   :status: [[check_linked_values('progress', 'status', 'in progress', one_hit=True )]]
   :collapse: False
Specification: result 4: Positive check thanks of one_hit option S_9ED38
status: progress
signature: check_linked_values
links outgoing: clv_A, clv_B, clv_C

Result 5: Two checks and a joint status Two checks are performed and both are positive. So their results get joined.

Example 10

.. spec:: result 5: Two checks and a joint status
   :links: clv_A, clv_B, clv_C
   :status: [[check_linked_values('progress', 'status', 'in progress', one_hit=True )]] [[check_linked_values('closed', 'status', 'closed', one_hit=True )]]
   :collapse: False
Specification: result 5: Two checks and a joint status S_951C8
status: progress closed
signature: check_linked_values
links outgoing: clv_A, clv_B, clv_C
Parameters:
  • result – value, which gets returned if all linked needs have parsed the checks

  • search_option – option name, which is used n linked needs for the search

  • search_value – value, which an option of a linked need must match

  • filter_string – Checks are only performed on linked needs, which pass the defined filter

  • one_hit – If True, only one linked need must have a positive check

Returns:

result, if all checks are positive

calc_sum

calc_sum(app: Sphinx, need: NeedItem | NeedPartItem | None, needs: NeedsMutable | NeedsView, option: str, filter: str | None = None, links_only: bool = False) float

Sums the values of a given option in filtered needs up to single number.

Useful e.g. for calculating the amount of needed hours for implementation of all linked specification needs.

Input data

Specification: Do this sum_input_1
signature: calc_sum
hours: 7
links incoming: R_D0791, R_3C95C
Specification: Do that sum_input_2
signature: calc_sum
hours: 15
Specification: Do too much sum_input_3
signature: calc_sum
hours: 110
links incoming: R_D0791, R_3C95C

Example 2

Example 11

.. req:: Result 1
   :amount: [[calc_sum("hours")]]
   :collapse: False
Requirement: Result 1 R_F7DEB
signature: calc_sum
amount: 147.0

Example 2

Example 12

.. req:: Result 2
   :amount: [[calc_sum("hours", "hours is not None and hours > 10")]]
   :collapse: False
Requirement: Result 2 R_96D5E
signature: calc_sum
amount: 137.0

Example 3

Example 13

.. req:: Result 3
   :links: sum_input_1; sum_input_3
   :amount: [[calc_sum("hours", links_only="True")]]
   :collapse: False
Requirement: Result 3 R_D0791
signature: calc_sum
amount: 117.0
links outgoing: sum_input_1, sum_input_3

Example 4

Example 14

.. req:: Result 4
   :links: sum_input_1; sum_input_3
   :amount: [[calc_sum("hours", "hours is not None and hours > 10", "True")]]
   :collapse: False
Requirement: Result 4 R_3C95C
signature: calc_sum
amount: 110.0
links outgoing: sum_input_1, sum_input_3
Parameters:
  • option – Options, from which the numbers shall be taken

  • filter – Filter string, which all needs must passed to get their value added.

  • links_only – If “True”, only linked needs are taken into account.

Returns:

A float number

Develop own functions

Registration

You must register every dynamic function by using the needs_functions configuration parameter, inside your conf.py file, to add a DynamicFunction:

def my_own_function(app, need, needs):
    return "Awesome"

needs_functions = [my_own_function]

Warning

Assigning a function to a Sphinx option will deactivate the incremental build feature of Sphinx. Please use the Sphinx-Needs API and read Incremental build support for details.

Recommended: You can use the following approach we used in our conf.py file to register dynamic functions:

from sphinx_needs.api import add_dynamic_function

   def my_function(app, need, needs, *args, **kwargs):
       # Do magic here
       return "some data"

   def setup(app):
         add_dynamic_function(app, my_function)

Restrictions

Variant functions

Added in version 1.0.2.

Needs variants add support for variants handling on need options.
The support for variants options introduce new ideologies on how to set values for need options.

To implement variants options, you can set a need option to a variant definition or multiple variant definitions. A variant definition can look like var_a:open or ['name' in tags]:assigned.

A variant definition has two parts: the rule or key and the value.
For example, if we specify a variant definition as var_a:open, then var_a is the key and open is the value. On the other hand, if we specify a variant definition as ['name' in tags]:assigned, then ['name' in tags] is the rule and assigned is the value.

Rules for specifying variant definitions

  • Variants must be wrapped in << and >> symbols, like <<var_a:open>>.

  • Variants gets checked from left to right.

  • When evaluating a variant definition, we use data from the current need object, Sphinx-Tags, and needs_filter_data as the context for filtering. Sphinx tags are injected under the name build_tags as a set of strings.

  • You can set a need option to multiple variant definitions by separating each definition with either the , symbol, like var_a:open, ['name' in tags]:assigned.|br| With multiple variant definitions, we set the first matching variant as the need option’s value.

  • When you set a need option to multiple variant definitions, you can specify the last definition as a default “variant-free” option which we can use if no variant definition matches.
    Example; In this multi-variant definitions, [status in tags]:added, var_a:changed, unknown, unknown will be used if none of the other variant definitions are True.

  • If you prefer your variant definitions to use rules instead of keys, then you should put your filter string inside square brackets like this: ['name' in tags]:assigned.

  • For multi-variant definitions, you can mix both rule and variant-named options like this: [author["test"][0:4] == 'me']:Me, var_a:Variant A, Unknown

To implement variants options, you must configure the following in your conf.py file:

Use Cases

There are various use cases for variants options support.

Use Case 1

In this example, you set the needs_variants configuration that comprises pre-defined variants assigned to “filter strings”. You can then use the keys in your needs_variants as references when defining variants for a need option.

For example, in your conf.py:

needs_variants = {
  "var_a": "'var_a' in build_tags"  # filter_string, check for Sphinx tags
  "var_b": "assignee == 'me'"
}

In your .rst file:

.. req:: Example
   :id: VA_001
   :status: <<var_a:open, var_b:closed, unknown>>

From the above example, if a need option has variants defined, then we get the filter string from the needs_variants configuration and evaluate it. If a variant definition is true, then we set the need option to the value of the variant definition.

Use Case 2

In this example, you can use the filter string directly in the need option’s variant definition.

For example, in your .rst file:

.. req:: Example
   :id: VA_002
   :status: <<['var_a' in tags]:open, [assignee == 'me']:closed, unknown>>

From the above example, we evaluate the filter string in our variant definition without referring to needs_variants. If a variant definition is true, then we set the need option to the value of the variant definition.

Use Case 3

In this example, you can use defined tags (via the -t command-line option or within conf.py, see here) in the need option’s variant definition.

First of all, define your Sphinx-Tags using either the -t command-line sphinx-build option:

sphinx-build -b html -t tag_a . _build

or using the special object named tags which is available in your Sphinx config file (conf.py file):

tags.add("tag_b")   # Add "tag_b" which is set to True

In your .rst file:

.. req:: Example
   :id: VA_003
   :status: <<['tag_a' in build_tags and 'tag_b' in build_tags]:open, closed>>

From the above example, if a tag is defined, the plugin can access it in the filter context when handling variants. If a variant definition is true, then we set the need option to the value of the variant definition.

Note

Undefined tags are false and defined tags are true.

Below is an implementation of variants for need options:

Example 15

.. req:: Variant options
   :id: VA_004
   :status: <<['variants' in tags and not collapse]:enabled, disabled>>
   :tags: variants;support
   :collapse:

   Variants for need options in action
Requirement: Variant options VA_004
status: disabled
tags: variants, support

Variants for need options in action