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: NeedsInfoType | 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: NeedsInfoType | 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: NeedsInfoType | 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: NeedsInfoType | 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
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: NeedsInfoType | 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.isdigit() and float(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.isdigit() and float(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