Skip to content
Back to top
View this page
Edit this page

# 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:

- `status`

- `tags`

- `style`

- `constraints`

- needs_extra_options

- needs_extra_links

- needs_global_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

    - filter – Filter 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

### links_from_content

links_from_content(app: Sphinx, need: NeedsInfoType | None, needs: NeedsMutable | NeedsView, need_id: str | None = None, filter: str | None = None) → list[str]

  Extracts links from content of a need.

  All need-links set by using `:need:`NEED_ID`` get extracted.

  Same links are only added once.

  Example:

  Requirement: Requirement 1 CON_REQ_1
  signature: links_from_content links incoming: CON_SPEC_1, CON_SPEC_2

  Requirement: Requirement 2 CON_REQ_2
  signature: links_from_content links incoming: CON_SPEC_1, CON_SPEC_2

  Specification: Test spec CON_SPEC_1
  signature: links_from_content links outgoing: CON_REQ_1, CON_REQ_2

  This specification cares about the realisation of:

  - Requirement 1 (CON_REQ_1)

  - My need (CON_REQ_2)

  Specification: Test spec 2 CON_SPEC_2
  signature: links_from_content links outgoing: CON_REQ_1, CON_REQ_2
  Links retrieved from content of Test spec (CON_SPEC_1)

  Used code of CON_SPEC_1:

      .. spec:: Test spec
         :id: CON_SPEC_1
         :links: [[links_from_content()]]

         This specification cares about the realisation of:

         * :need:`CON_REQ_1`
         * :need:`CON_REQ_2`

      .. spec:: Test spec 2
         :id: CON_SPEC_2
         :links: [[links_from_content('CON_SPEC_1')]]

         Links retrieved from content of :need:`CON_SPEC_1`

  Parameters:

    - need_id – ID of need, which provides the content. If not set, current need is used.

    - filter – Filter string, which a found need-link must pass.

  Returns:

    List of linked need-ids in content

## 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

#### incoming_links

Incoming links are not available when dynamic functions gets calculated.

That’s because a dynamic function can change outgoing links, so that the incoming links of the target need will be recalculated. This is automatically done but not until all dynamic functions are resolved.