Dynamic functions

Sphinx-Needs provides a mechanism to set dynamic data for need-options during generation. We do this by giving an author the possibility to set a function call to a predefined function, which calculates the final result/value for the option.

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.

needtable

The options style_row of needtable also support dynamic function execution. In this case, the function gets executed with the found need for each row.

This allows you to set row and column specific styles such as, set a row background to red, if a need-status is failed.

Example

.. req:: my test requirement
   :id: df_1
   :status: open

   This need has the id **[[copy("id")]]** and status **[[copy("status")]]**.
Requirement: my test requirement df_1 _images/arrow-right-circle.svg
status: open

This need has id df_1 and status open.

Built-in functions

The following functions are available in all Sphinx-Needs installations.

Note

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

test

test(app: Sphinx, need: NeedsInfoType, needs: dict[str, NeedsInfoType], *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.

.. req:: test requirement

    [[test('arg_1', [1,2,3], my_keyword='awesome')]]
Requirement: test requirement R_A6A4E _images/arrow-right-circle.svg
signature: test

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

Returns:

single test string

echo

echo(app: Sphinx, need: NeedsInfoType, needs: dict[str, NeedsInfoType], text: str, *args: Any, **kwargs: Any) str

New in version 0.6.3.

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

A nice :need_func:`[[echo("first")]] test` for need_func.

Result: A nice first test for need_func.

copy

copy(app: Sphinx, need: NeedsInfoType, needs: dict[str, NeedsInfoType], 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

.. 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 _images/arrow-right-circle.svg
status: open
tags: tag_1, tag_2, tag_3
signature: copy
links incoming: copy_2, copy_3
Specification: copy-example implementation copy_2 _images/arrow-right-circle.svg
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 _images/arrow-right-circle.svg
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 es the title of the first need found under the same highest
   section (headline):

   [[copy('title', filter='current_need["sections"][-1]==sections[-1]')]]
Test Case: test of current_need value copy_4 _images/arrow-right-circle.svg
signature: copy

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

my test requirement

This filter possibilities get really powerful in combination with needs_global_options.

Parameters:
option: str

Name of the option to copy

need_id: str | None = None

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

upper: bool = False

Is set to True, copied value will be uppercase

lower: bool = False

Is set to True, copied value will be lowercase

filter: str | None = None

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, needs: dict[str, NeedsInfoType], 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

.. 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 _images/arrow-right-circle.svg
status: in progress
signature: check_linked_values
links incoming: clv_1, clv_2, clv_3, clv_4, clv_5
Requirement: Input B clv_B _images/arrow-right-circle.svg
status: in progress
signature: check_linked_values
links incoming: clv_1, clv_2, clv_3, clv_4, clv_5
Specification: Input C clv_C _images/arrow-right-circle.svg
status: closed
signature: check_linked_values
links incoming: clv_2, clv_3, clv_4, clv_5

Example 1: Positive check

Status gets set to progress.

.. spec:: result 1: Positive check
   :links: clv_A, clv_B
   :status: [[check_linked_values('progress', 'status', 'in progress' )]]
Specification: result 1: Positive check clv_1 _images/arrow-right-circle.svg
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”.

.. spec:: result 2: Negative check
   :links: clv_A, clv_B, clv_C
   :status: [[check_linked_values('progress', 'status', 'in progress' )]]
Specification: result 2: Negative check clv_2 _images/arrow-right-circle.svg
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.

.. 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" ' )]]
Specification: result 3: Positive check thanks of used filter clv_3 _images/arrow-right-circle.svg
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.

.. 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 )]]
Specification: result 4: Positive check thanks of one_hit option clv_4 _images/arrow-right-circle.svg
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.

.. 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 )]]
Specification: result 5: Two checks and a joint status clv_5 _images/arrow-right-circle.svg
status: progress closed
signature: check_linked_values
links outgoing: clv_A, clv_B, clv_C
Parameters:
result: Any

value, which gets returned if all linked needs have parsed the checks

search_option: str

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

search_value: Any

value, which an option of a linked need must match

filter_string: str | None = None

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

one_hit: bool = False

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, needs: dict[str, NeedsInfoType], 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 _images/arrow-right-circle.svg
signature: calc_sum
hours: 7
links incoming: R_D0791, R_3C95C
Specification: Do that sum_input_2 _images/arrow-right-circle.svg
signature: calc_sum
hours: 15
Specification: Do too much sum_input_3 _images/arrow-right-circle.svg
signature: calc_sum
hours: 110
links incoming: R_D0791, R_3C95C

Example 2

.. req:: Result 1
   :amount: [[calc_sum("hours")]]
Requirement: Result 1 R_F7DEB _images/arrow-right-circle.svg
signature: calc_sum
amount: 147.0

Example 2

.. req:: Result 2
   :amount: [[calc_sum("hours", "hours.isdigit() and float(hours) > 10")]]
Requirement: Result 2 R_96D5E _images/arrow-right-circle.svg
signature: calc_sum
amount: 137.0

Example 3

.. req:: Result 3
   :links: sum_input_1; sum_input_3
   :amount: [[calc_sum("hours", links_only="True")]]
Requirement: Result 3 R_D0791 _images/arrow-right-circle.svg
signature: calc_sum
amount: 117.0
links outgoing: sum_input_1, sum_input_3

Example 4

.. req:: Result 4
   :links: sum_input_1; sum_input_3
   :amount: [[calc_sum("hours", "hours.isdigit() and float(hours) > 10", "True")]]
Requirement: Result 4 R_3C95C _images/arrow-right-circle.svg
signature: calc_sum
amount: 110.0
links outgoing: sum_input_1, sum_input_3
Parameters:
option: str

Options, from which the numbers shall be taken

filter: str | None = None

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

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

Returns:

A float number

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 _images/arrow-right-circle.svg
signature: links_from_content
links incoming: CON_SPEC_1, CON_SPEC_2
Requirement: Requirement 2 CON_REQ_2 _images/arrow-right-circle.svg
signature: links_from_content
links incoming: CON_SPEC_1, CON_SPEC_2
Specification: Test spec CON_SPEC_1 _images/arrow-right-circle.svg
signature: links_from_content
links outgoing: CON_REQ_1, CON_REQ_2

This specification cares about the realisation of:

Specification: Test spec 2 CON_SPEC_2 _images/arrow-right-circle.svg
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`

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

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

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:

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)

Reference function

def test(app, need, needs, *args, **kwargs):
    """
    :param app: sphinx app
    :param need: current need
    :param needs: dictionary of all needs. Key is the need id
    :return: str,int,float or list of elements of type str,int,float
    """
    # where the magic happens
    return "awesome"

You can call the defined function via:

.. req:: test requirement
   :status: [[test()]]

The parameters app, need and needs are set automatically. You are free to add other parameters, which must be of type str, int, float and list.

need structure

need = {
   'docname': str: document name,
   'lineno': int: linenumber,
   'links_back': list: list of incoming links (see restrictions),
   'target_node': node: sphinx target node for internal references,
   'type': str: short name of type,
   'type_name': str: long name of type,
   'type_prefix': str: name of type prefix,
   'type_color': str: type color,
   'type_style': str: type style,
   'status': str: status,
   'tags': list: list of tags,
   'id': str: id,
   'links': list: list of outgoing links,
   'title': str: trimmed title of need,
   'full_title': str: full title of string,
   'content': str: unparsed need content,
   'collapse': bool: true if meta data shall be collapsed,
   'hide': bool: true if complete need shall be hidden,
}

Adding new keywords to need object will be treated as extra_option in need meta area.

Restrictions

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.