-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change repeated capabilities #718
Conversation
build/templates/session.py.mako
Outdated
def __getitem__(self, repeated_capability): | ||
'''Set/get properties or call methods with a repeated capability (i.e. channels)''' | ||
if isinstance(repeated_capability, list): | ||
rep_cap_list = [str(r) if str(r).startswith('${rep_cap['prefix']}') else '${rep_cap['prefix']}' + str(r) for r in repeated_capability] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... IVI repeated capability strings are case insensitive, but the prefix check here is case sensitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made check case insensitive.
CHANGELOG.md
Outdated
session = niscope.Session('PXI1Slot2') | ||
|
||
# Channel repeated capabilities | ||
session.channel['0'].channel_enabled = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this is a container that you're indexing, shouldn't it be in plural form ("channels")?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to channels
CHANGELOG.md
Outdated
session.channel[[0, 1, 3]].channel_enabled = True | ||
wfm = session.channel[[0, 1, 3]].fetch(5000) | ||
|
||
# P2P repeated capabilities |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2P is not supported, right? Maybe use NI-FGEN MarkerEvents for the example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add P2P attributes back.
CHANGELOG.md
Outdated
* #### Changed | ||
* #### Removed | ||
* ### NI-FGEN | ||
* #### Added | ||
* Channel repeated capability | ||
* P2P repeated capability |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we took all P2P stuff out of the Python bindings.
CHANGELOG.md
Outdated
@@ -29,14 +49,19 @@ All notable changes to this project will be documented in this file. | |||
* #### Removed | |||
* ### NI-DCPower | |||
* #### Added | |||
* Channel repeated capability | |||
* #### Changed | |||
* #### Removed | |||
* ### NI-FGEN | |||
* #### Added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script Triggers, Marker Events are two NI-FGEN and NI-HSDIO repeated capabilities. Don't know of others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
build/helper/documentation_helper.py
Outdated
@@ -402,7 +402,7 @@ def _format_type_for_docstring(param, numpy, config): | |||
|
|||
|
|||
rep_cap_method_desc_docstring = rep_cap_method_desc + ''' | |||
session['0,1'].{1}({2}) | |||
session.channel[[0, 1]].{1}({2}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question for the Python crowd... is indexing using an array "Pythonic"? Or is it strange?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely weird to me.
Looking at the documentation of __getitem__
it says "For sequence types, the accepted keys should be integers and slice objects.".
So here the user should be able to say session.channel[0:1]
(since 0:1
represents a slice).
We'd have to give it more thought if you intend to support the user passing in channels that don't fall under the umbrella of "integer or slice" (I.e. channels 0, 1, 4, and 12). But depending on what __getitem__
returns you could try and support something like my_channels = session.channels[0:1] + session.channels[4] + session.channels[12]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Channels 0, 1, 4, 12 definitely needs to be supported. Something like my_channels = session.channels[0:1] + session.channels[4] + session.channels[12]
to do it though seems like a step backwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you wanted a list that contains elements at indices 0, 1, 4, and 12, how would you construct that list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indexing using arrays is not strange at all. See Numpy's advanced indexing feature: https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.indexing.html#advanced-indexing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After seeing numpy's "advanced-indexing" I think allowing an array makes sense. I think as long as the user can use integers and slices then users will find themselves successful. Allowing arrays seems like a "nice-to-have" but if it is easy to support, and the user can easily understand what happens when an array is used, then I say go for it.
We need one object where we can internally put together a string that looks like '0,1,4,12'.
The string is an implementation detail. What you want is for the user to be able to say "on channels x do y". Since we're exposing the "seuqence" version of __getitem__
on the channels
attribute to access specific channels, I see it the same as accessing indices of a sequence type to operate on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is an implementation detail, but it is an important detail. We need the call to condense down to a single call with '0,1,4,12' not a call with '0,1', then a call with '4', then a call with '12'.
It is very possible that I don't see something that would allow my_channels = session.channels[0:1] + session.channels[4] + session.channels[12]
to do this. But also, this seems very verbose.
(session.channels[0:1] + session.channels[4] + session.channels[12]).fetch(1000)
seems unwieldy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can probably be achieved, if we decide that's the way we want to go, by implementing add().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string is an implementation detail.
Under the hood, this will touch hardware. If a user sets the voltage on several channels "all at once" but the Python code somewhat serializes this, DUT can be damaged. Mark is right that we need to make this work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree serializing the calls is not an option. I just want to make sure the implementation details don't bleed out into the interface. After seeing that numpy uses arrays to mean a list of indices, this is starting to look less weird.
I also don't think add()
should be added to the type that __getitem__()
returns just yet (mainly because the list-of-indices should cover most of the use cases). I just wanted to bring it up to show the similarities between two sequence types (yours and lists), to help us think about how we should design the interface,
Just make sure the normal use case isn't broken (integers and slices), and the array-of-indices has good documentation and I think you're good to go. 👍
build/templates/session.py.mako
Outdated
else: | ||
rep_cap_list = [str(repeated_capability) if str(repeated_capability).lower().startswith('${rep_cap['prefix'].lower()}') else '${rep_cap['prefix']}' + str(repeated_capability)] | ||
|
||
return _RepeatedCapbilities(${config['session_handle_parameter_name']}=self._${config['session_handle_parameter_name']}, repeated_capability=','.join(rep_cap_list), library=self._library, encoding=self._encoding, freeze_it=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_RepeatedCapbilities
typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo fixed
generated/nidcpower/session.py
Outdated
return _RepeatedCapbilities(vi=self._vi, repeated_capability=','.join(rep_cap_list), library=self._library, encoding=self._encoding, freeze_it=True) | ||
|
||
|
||
class _RepeatedCapbilities(object): | ||
'''Base class for all NI-DCPower sessions.''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems odd that a class named _RepeatedCapbilities is 'Base class for all NI-DCPower sessions.'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should it be called? It is the class that contains any attribute (all) or function that takes a repeated capabilities string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't come up with a perfect name for it but _SessionBase is probably better than _RepeatedCapabilities.
When I read _RepeatedCapabilities, I think of a collection of repeated capabilities such as "0" or "ScriptTrigger2".
build/templates/session.py.mako
Outdated
|
||
def __getitem__(self, repeated_capability): | ||
'''Set/get properties or call methods with a repeated capability (i.e. channels)''' | ||
if isinstance(repeated_capability, list): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems restrictive. Clients should be able to pass anything iterable even if not a list. For example, range() doesn't return a list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use duck typing. Anything iterable will now work.
There is a scope system test failing because it sets
|
CHANGELOG.md
Outdated
* #### Added | ||
* Repeated capablilites - See #737 for discussion: | ||
* `channel` repeated capability | ||
* `p2p_streams` repeated capability |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since #724 is not merged yet, nor is it decided upon, you should remove p2p_streams from this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
.. code-block:: python | ||
|
||
import nifgen | ||
session = nifgen.Session('PXI1Slot2') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how come you didn't use with
? If you'd still rather not, then maybe show how you close the session?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't trying to show how to write an actual useful program. Added session.close()
README.rst
Outdated
session.channels[:8].channel_enabled = True # channels 0, 1, 2, 3, 4, 5, 6, 7 | ||
wfm = session.channels[[0, 1, 3]].fetch(5000) | ||
|
||
# P2P repeated capabilities |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bad comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
README.rst
Outdated
wfm = session.channels[[0, 1, 3]].fetch(5000) | ||
|
||
# P2P repeated capabilities | ||
i = session.script_triggers['0'].SCRIPT_TRIGGERS_COUNT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use python_names for the attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
README.rst
Outdated
i = session.script_triggers['0'].SCRIPT_TRIGGERS_COUNT | ||
i = session.script_triggers[0].SCRIPT_TRIGGERS_COUNT | ||
i = session.script_triggers[[0, 1, 3]].SCRIPT_TRIGGERS_COUNT | ||
i = session.script_triggers['ScriptTrigger0'].SCRIPT_TRIGGERS_COUNT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this actually work? you don't get "ScriptTriggerScriptTrigger0" passed into the C function?
If it works, I'd consider it an implementation fluke and would not document it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this works and it works by design. I explicitly look for the prefix and only add if it is not there.
Do you think we should never accept strings that already have the prefix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we should never accept strings that already have the prefix?
Well, the whole point of these containers is for the client to not have to remember magic strings. I don't think it needs to be documented, even if we still accept them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, removed from readme.
@@ -5,32 +5,36 @@ class ParameterUsageOptions(Enum): | |||
'''Different usage options for parameter lists.''' | |||
|
|||
SESSION_METHOD_DECLARATION = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should just use auto here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto()
is only available in 3.6 and the enum backport. I used the solution given in the 3.5 documentation:
class AutoNumber(Enum):
def __new__(cls):
value = len(cls.__members__) + 1
obj = object.__new__(cls)
obj._value_ = value
return obj
class ParameterUsageOptions(AutoNumber):
'''Different usage options for parameter lists.'''
SESSION_METHOD_DECLARATION = ()
'''For declaring a regular method in Session'''
SESSION_INIT_DECLARATION = ()```
build/templates/_converters.py.mako
Outdated
@@ -7,6 +7,26 @@ from ${module_name} import visatype | |||
import datetime | |||
|
|||
|
|||
def convert_repeated_capabilities(repeated_capability, prefix=''): | |||
# First try it as a list | |||
if isinstance(repeated_capability, list): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be too restrictive. What if the structure is something other than a list, like array.array? Python user would expect it to work because duck typing.
Consider implementing with try/except pattern instead of looking for specific type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did that at first. This is fixing the issue that broke using string. Strings are iterable so it was iterating over the string and that is what would turn '0,1' into '0,,,1' and 'ScriptTrigger0' into 'S,c,r,i,p,t,T,r,i,g,g,e,r,0'.
I would need to explicitly look for a string and handle that first, and then try to iterate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the logic to first look for six.string_type or six.text_type. This allows us to accept any iterable.
This adds a dependency to six for all drivers.
build/templates/_converters.py.mako
Outdated
try: | ||
def ifnone(a, b): | ||
return b if a is None else a | ||
# Turn the slice into a list so we can iterate over it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you turn the slice into a list, can't you just call this function with the list recursively?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
generated/nifake/session.py
Outdated
@@ -113,10 +140,16 @@ class _SessionBase(object): | |||
An attribute of type string with read/write access. | |||
''' | |||
|
|||
def __init__(self, repeated_capability): | |||
self._library = library_singleton.get() | |||
def __init__(self, repeated_capability, vi=None, library=None, encoding=None, freeze_it=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's less error prone if they weren't optional and Session passes them when it chains up to its parent class' __init__()
method.
Adds dependency on six to the drivers
[X] This contribution adheres to CONTRIBUTING.md.
[X] I've updated CHANGELOG.md if applicable.
[X] I've added tests applicable for this pull request
What does this Pull Request accomplish?
List issues fixed by this Pull Request below, if any.What testing has been done?