Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions build/helper/metadata_add_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,38 @@ def add_all_config_metadata(config):
'''
config = merge_helper(config, 'config', config, use_re=False)

for repeated_capability in config['repeated_capabilities']:
documentation = repeated_capability.setdefault('documentation', {})
prefix = repeated_capability['prefix']
name = repeated_capability['python_name']

if prefix:
documentation.setdefault(
'description',
(
'If no prefix is added to the items in the parameter, the correct prefix will be added when\n'
'the driver function call is made.\n\n'
".. code:: python\n\n session.{}['0-2'].channel_enabled = True\n\n"
"passes a string of :python:`'{}0, {}1, {}2'` to the set attribute function.\n\n"
'If an invalid repeated capability is passed to the driver, the driver will return an error.\n\n'
'You can also explicitly use the prefix as part of the parameter, but it must be the correct prefix\n'
'for the specific repeated capability.'
).format(name, prefix, prefix, prefix)
)
else:
documentation.setdefault('description', '')

documentation.setdefault(
'examples',
[
(
"session.{}['{}0-{}2'].channel_enabled = True\n\n"
"passes a string of :python:`'{}0, {}1, {}2'` to the set attribute function."
).format(name, prefix, prefix, prefix, prefix, prefix)
]
)
documentation.setdefault('valid_indices', [])

if 'use_locking' not in config:
config['use_locking'] = True

Expand Down
29 changes: 13 additions & 16 deletions build/templates/rep_caps.rst.mako
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<%
import build.helper as helper
import re

config = template_parameters['metadata'].config
module_name = config['module_name']
Expand Down Expand Up @@ -33,34 +34,30 @@ ${helper.get_rst_header_snippet('Repeated Capabilities', '=')}
% for rep_cap in config['repeated_capabilities']:
<%
name = rep_cap['python_name']
prefix = rep_cap['prefix']
rep_cap_doc = rep_cap['documentation']
%>\
${helper.get_rst_header_snippet(name, '-')}

.. py:attribute:: ${module_name}.Session.${name}[]

% if len(prefix) > 0:
If no prefix is added to the items in the parameter, the correct prefix will be added when
the driver function call is made.
% if rep_cap_doc['description']:
${' ' + re.sub(r'\n(?=[^\n])', '\n ', rep_cap_doc['description'])}

.. code:: python

session.${name}['0-2'].channel_enabled = True

passes a string of :python:`'${prefix}0, ${prefix}1, ${prefix}2'` to the set attribute function.

If an invalid repeated capability is passed to the driver, the driver will return an error.

You can also explicitly use the prefix as part of the parameter, but it must be the correct prefix
for the specific repeated capability.
% endif
% if rep_cap_doc['valid_indices']:
Valid Indices: :python:`'${", ".join(rep_cap_doc["valid_indices"])}'`.

% endif
% for example in rep_cap_doc['examples']:
.. code:: python

session.${name}['${prefix}0-${prefix}2'].channel_enabled = True
${example.split('\n\n', 1)[0].replace('\n', '\n ')}

passes a string of :python:`'${prefix}0, ${prefix}1, ${prefix}2'` to the set attribute function.
% if '\n\n' in example:
${' ' + re.sub(r'\n(?=[^\n])', '\n ', example.split('\n\n', 1)[1])}

% endif
% endfor

% endfor

13 changes: 12 additions & 1 deletion build/unit_tests/test_metadata_add_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,18 @@ def _compare_dicts(actual, expected):
],
'enum_whitelist_suffix': ['_POINT_FIVE'],
'repeated_capabilities': [
{'python_name': 'channels', 'prefix': '', },
{
'python_name': 'channels',
'prefix': '',
'documentation': {
'description': '',
'examples': [
"session.channels['0-2'].channel_enabled = True\n\n"
"passes a string of :python:`'0, 1, 2'` to the set attribute function.",
],
'valid_indices': [],
},
},
],
'use_locking': True,
'functions': functions_expected,
Expand Down
102 changes: 102 additions & 0 deletions build/unit_tests/test_rep_caps_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace

from build.generate_template import generate_template
from build.helper.metadata_add_all import add_all_config_metadata


def _render_rep_caps(config):
repo_root = Path(__file__).resolve().parents[2]
template_path = repo_root / 'build' / 'templates' / 'rep_caps.rst.mako'
metadata = SimpleNamespace(config=add_all_config_metadata(config))
with TemporaryDirectory() as temp_dir:
output_path = Path(temp_dir) / 'rep_caps.rst'
generate_template(str(template_path), {'metadata': metadata}, str(output_path))
return output_path.read_text()


def test_rep_caps_template_uses_custom_documentation_overrides():
config = {
'module_name': 'nifake',
'c_function_prefix': 'niFake_',
'repeated_capabilities': [
{
'prefix': 'res',
'python_name': 'resources',
'documentation': {
'description': 'Resource repeated capabilities use fully-qualified identifiers.',
'valid_indices': ['dev0/res0', 'dev0/res1'],
'examples': [
"session.resources['dev0/res0'].channel_enabled = True",
"session.resources['dev0/res1'].channel_enabled = True",
],
},
}
],
}

rendered = _render_rep_caps(config)

assert 'Resource repeated capabilities use fully-qualified identifiers.' in rendered
assert "Valid Indices: :python:`'dev0/res0, dev0/res1'`." in rendered
assert "session.resources['dev0/res0'].channel_enabled = True" in rendered
assert "session.resources['dev0/res1'].channel_enabled = True" in rendered

# Generic auto-prefix guidance should be suppressed when override disables it.
assert 'If no prefix is added to the items in the parameter' not in rendered
assert "session.resources['0-2'].channel_enabled = True" not in rendered
assert "'res0, res1, res2'" not in rendered


def test_rep_caps_template_preserves_default_prefixed_behavior():
config = {
'module_name': 'nifake',
'c_function_prefix': 'niFake_',
'repeated_capabilities': [
{
'prefix': 'channel',
'python_name': 'channels',
},
{
'prefix': '',
'python_name': 'instruments',
}
],
}

rendered = _render_rep_caps(config)

assert 'If no prefix is added to the items in the parameter' in rendered
assert "session.channels['0-2'].channel_enabled = True" in rendered
assert "'channel0, channel1, channel2'" in rendered
example_description = (
" passes a string of :python:`'channel0, channel1, channel2'` to the set attribute function."
)
assert rendered.count(example_description) == 2
assert '\n passes a string' not in rendered
assert "set attribute function.\n\n\ninstruments\n" in rendered
assert rendered.endswith('\n\n\n\n')
assert not any(line.isspace() for line in rendered.splitlines())


def test_rep_caps_template_expands_default_documentation_fields():
config = {
'module_name': 'nifake',
'c_function_prefix': 'niFake_',
'repeated_capabilities': [
{
'prefix': 'channel',
'python_name': 'channels',
'documentation': {
'description': 'Custom channel documentation.',
},
}
],
}

rendered = _render_rep_caps(config)

assert 'Custom channel documentation.' in rendered
assert "session.channels['channel0-channel2'].channel_enabled = True" in rendered
assert "'channel0, channel1, channel2'" in rendered
4 changes: 4 additions & 0 deletions generated/nifake/nifake/unit_tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ def test_chained_repeated_capabilities_list(self):
with nifake.Session('dev1') as session:
assert session.sites[0, 1].channels[2, 3]._repeated_capability_list == ['site0/2', 'site0/3', 'site1/2', 'site1/3']

def test_multi_instrument_chained_repeated_capabilities_list(self):
with nifake.Session('dev1,dev2') as session:
assert session.instruments['dev1', 'dev2'].sites[0, 1]._repeated_capability_list == ['dev1/site0', 'dev1/site1', 'dev2/site0', 'dev2/site1']

def test_chained_repeated_capability_method_on_specific_channel(self):
test_maximum_time_ms = 10 # milliseconds
test_maximum_time = hightime.timedelta(milliseconds=test_maximum_time_ms)
Expand Down
8 changes: 8 additions & 0 deletions src/nifake/metadata/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
'python_name': 'channels'
},
{
'documentation': {
'description': 'Sites are identified by the ``site`` prefix followed by a zero-based index.',
'valid_indices': ['0', '1', '2', '3', '4'],
'examples': [
"session.sites['site0'].function_with_repeated_capability_type()",
"session.sites['site0', 'site1'].function_with_repeated_capability_type()",
],
},
'prefix': 'site',
'python_name': 'sites'
},
Expand Down
4 changes: 4 additions & 0 deletions src/nifake/unit_tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ def test_chained_repeated_capabilities_list(self):
with nifake.Session('dev1') as session:
assert session.sites[0, 1].channels[2, 3]._repeated_capability_list == ['site0/2', 'site0/3', 'site1/2', 'site1/3']

def test_multi_instrument_chained_repeated_capabilities_list(self):
with nifake.Session('dev1,dev2') as session:
assert session.instruments['dev1', 'dev2'].sites[0, 1]._repeated_capability_list == ['dev1/site0', 'dev1/site1', 'dev2/site0', 'dev2/site1']

def test_chained_repeated_capability_method_on_specific_channel(self):
test_maximum_time_ms = 10 # milliseconds
test_maximum_time = hightime.timedelta(milliseconds=test_maximum_time_ms)
Expand Down
Loading