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")`.
This need has id df_1 and status open. |
Dynamic functions can be used for the following directive options:
status
tags
style
constraints
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')`
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.
Copies status of
copy_1
to own status. Sets also a comment, which copies the id of own need.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]')`
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
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
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
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
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
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
- 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
Example 2
Example 11
.. req:: Result 1 :amount: [[calc_sum("hours")]] :collapse: False
Example 2
Example 12
.. req:: Result 2 :amount: [[calc_sum("hours", "hours.isdigit() and float(hours) > 10")]] :collapse: False
Example 3
Example 13
.. req:: Result 3 :links: sum_input_1; sum_input_3 :amount: [[calc_sum("hours", links_only="True")]] :collapse: False
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
- 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:
This specification cares about the realisation of:
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.