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")`.
This need has id df_1 and status open. |
Dynamic functions can be used for the following directive options:
statustagsstylelayoutconstraints
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')`
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.
Copies status of
copy_1to own status. Sets also a comment, which copies the id of own need.Set own link to
copy_2and 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: 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
filteroption.If
one_hitis 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_hitis 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: 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
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 is not None and 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 is not None and 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: NeedItem | NeedPartItem | 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.
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_tagsas a set of strings.You can set a need option to multiple variant definitions by separating each definition with either the
,symbol, likevar_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
Variants for need options in action |