Skip to content
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

Merged
merged 66 commits into from
Feb 8, 2018
Merged

Change repeated capabilities #718

merged 66 commits into from
Feb 8, 2018

Conversation

texasaggie97-zz
Copy link
Contributor

[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?

  • Moves repeated capabilities away from session object and on to specific instances of the different types
  • Repeated capability can be integer or string, single item or list

List issues fixed by this Pull Request below, if any.

What testing has been done?

  • System
  • Unit

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]
Copy link

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.

Copy link
Contributor Author

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
Copy link
Member

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")?

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@@ -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})
Copy link
Member

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?

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].

Copy link
Contributor Author

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.

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?

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

Copy link

@thejcannon thejcannon Jan 16, 2018

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.

Copy link
Contributor Author

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.

Copy link
Member

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().

Copy link
Member

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.

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. 👍

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_RepeatedCapbilities

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo fixed

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.'''
Copy link
Member

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.'

Copy link
Contributor Author

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.

Copy link
Member

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".


def __getitem__(self, repeated_capability):
'''Set/get properties or call methods with a repeated capability (i.e. channels)'''
if isinstance(repeated_capability, list):
Copy link
Member

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.

Copy link
Contributor Author

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.

@texasaggie97-zz
Copy link
Contributor Author

There is a scope system test failing because it sets test_channels = '0,1'. Should I

  1. Fix the test to say test_channels = range(2)
  2. Fix the code to allow it?

@texasaggie97-zz texasaggie97-zz dismissed marcoskirsch’s stale review February 5, 2018 21:15

All comments addressed

CHANGELOG.md Outdated
* #### Added
* Repeated capablilites - See #737 for discussion:
* `channel` repeated capability
* `p2p_streams` repeated capability
Copy link
Member

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.

Copy link
Contributor Author

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')
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad comment

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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 = ()```

@@ -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):
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

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
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -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):
Copy link
Member

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.

@marcoskirsch marcoskirsch changed the title [2]Change repeated capabilities Change repeated capabilities Feb 7, 2018
@texasaggie97-zz texasaggie97-zz dismissed marcoskirsch’s stale review February 8, 2018 16:35

All comments addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants