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

Harmonise instrument name definition pattern, consistently name the instrument connection argument "adapter" #659

Merged
merged 5 commits into from Nov 29, 2022

Conversation

BenediktBurger
Copy link
Member

@BenediktBurger BenediktBurger commented Aug 2, 2022

Fixes #658 and adjustes the documentation accordingly.

  • Verify that the whole adding_instruments documentation is up to date.
  • Create a follow up Issue for changing all instruments. (correct the super invocation in Keysight N7776, LakeShore421), maybe together with the change to a "SCPI" mixin.

@bilderbuchi
Copy link
Member

bilderbuchi commented Aug 2, 2022

I'm actually not sure if we should always put the default name into the constructor signature, as it pollutes that (and displayed docstrings, presumably) with a pretty long entry that is not that central.
I'd prefer we use the setdefault to set a reasonable default name (i.e. the model identifier) in the constructor, and allow the user to easily override it with a setup-specific name if desired:

def test_name_setting():
    class NamedInstrument(Instrument):
        def __init__(self, adapter, **kwargs):
            kwargs.setdefault('name', 'Default Instrument')
            super().__init__(adapter, **kwargs)

    adapter = FakeAdapter()
    instr = NamedInstrument(adapter)
    assert instr.name == 'Default Instrument'
    # Let's use a user-specific name (e.g. to disambiguate multiple identical devices)
    instr2 = NamedInstrument(adapter, name='Vacuum Frobnicator')
    assert instr2.name == 'Vacuum Frobnicator'

This way the signature of NamedInstrument stays clean, but we remain as flexible as possible.
What do you think?

@BenediktBurger
Copy link
Member Author

Normally I prefer to know, what values I might change and what the default is. setdefault hides the possibility to rename the device.
As changing the name is rarely used (almost no instrument allowed for that), I'm fine with hiding the renaming option.

One caveat though: The documentation does not hand the kwargs to the instrument, but to the adapter in case of a custom adapter!
We should take care of that as well.
We might even remove that example, as custom adapters are rarely necessary and should not be used, instead read/write.

Once I'm remodeling all instruments. Should we name the adapter consistently adapter? In some instruments it is called "resourceName", which is taken from the documentation.

@bilderbuchi
Copy link
Member

One caveat though: The documentation does not hand the kwargs to the instrument, but to the adapter in case of a custom adapter!

I'm not sure what you mean?

Once I'm remodeling all instruments. Should we name the adapter consistently adapter? In some instruments it is called "resourceName", which is taken from the documentation.

I think this comes from pyvisa nomenclature, where it's "resource name". I'm not sure. I guess in 95% of cases people supply a resource name string, so that would be a more fitting name. However, if people pass an Adapter instance, resourceName is a weird name. :-P -- adapter is delightfully ambiguous 😒
@msmttchr what do you think?

adapter is also consistent with Instrument. Renaming the Instrument posarg will probably break a lot of code I suspect.

@msmttchr
Copy link
Member

msmttchr commented Aug 2, 2022

Once I'm remodeling all instruments. Should we name the adapter consistently adapter? In some instruments it is called "resourceName", which is taken from the documentation.

I think this comes from pyvisa nomenclature, where it's "resource name". I'm not sure. I guess in 95% of cases people supply a resource name string, so that would be a more fitting name. However, if people pass an Adapter instance, resourceName is a weird name. :-P -- adapter is delightfully ambiguous 😒 @msmttchr what do you think?

adapter is also consistent with Instrument.

I agree, in my opinion adapter is the correct name.

@BenediktBurger
Copy link
Member Author

I made the talked about changes (to adapter and using kwargs).

There are some doctest errors in the documentation though.

docs/dev/adding_instruments.rst Outdated Show resolved Hide resolved
pymeasure/instruments/activetechnologies/AWG401x.py Outdated Show resolved Hide resolved
pymeasure/instruments/agilent/agilent33521A.py Outdated Show resolved Hide resolved
docs/dev/adding_instruments.rst Outdated Show resolved Hide resolved
pymeasure/instruments/attocube/anc300.py Outdated Show resolved Hide resolved
@bilderbuchi
Copy link
Member

bilderbuchi commented Aug 3, 2022

I'm actually not sure if we should always put the default name into the constructor signature, as it pollutes that (and displayed docstrings, presumably) with a pretty long entry that is not that central. I'd prefer we use the setdefault to set a reasonable default name (i.e. the model identifier) in the constructor, and allow the user to easily override it with a setup-specific name if desired:

def test_name_setting():
    class NamedInstrument(Instrument):
        def __init__(self, adapter, **kwargs):
            kwargs.setdefault('name', 'Default Instrument')
            super().__init__(adapter, **kwargs)

    adapter = FakeAdapter()
    instr = NamedInstrument(adapter)
    assert instr.name == 'Default Instrument'
    # Let's use a user-specific name (e.g. to disambiguate multiple identical devices)
    instr2 = NamedInstrument(adapter, name='Vacuum Frobnicator')
    assert instr2.name == 'Vacuum Frobnicator'

This way the signature of NamedInstrument stays clean, but we remain as flexible as possible. What do you think?

Please also add that test to test_instrument.py, it's good to have tests that define best practices, so they constantly get checked/confirmed.

@bilderbuchi
Copy link
Member

bilderbuchi commented Aug 3, 2022

I don't know what's up with the doctest, first I thought that it's because we swap in FakeInstrument in adding_instruments.rst L89 (to enable doctesting), but I can't seem to reproduce this locally, and the error message does not make sense to me. Let's see if it's a transient or appears again.

@bilderbuchi bilderbuchi changed the title Fix 658 Harmonise instrument name definition pattern, consistently name the instrument connection argument "adapter" Aug 3, 2022
@BenediktBurger
Copy link
Member Author

Thanks for your reviews. I implemented the changes.

I also implemented a test whether a named argument removes the corresponding entry from kwargs.

@bilderbuchi
Copy link
Member

bilderbuchi commented Aug 3, 2022

I think we're good now! Only the doctests are still broken (and a small linting issue remains). 😿 I'll pull and see if I can figure it out locally.

Afterwards, we should check/update all open PRs for the new usage (adapter and name handling). I don't know how to meaningfully automate those checks. 😞

docs/dev/adding_instruments.rst Outdated Show resolved Hide resolved
pymeasure/instruments/activetechnologies/AWG401x.py Outdated Show resolved Hide resolved
pymeasure/instruments/agilent/agilent33521A.py Outdated Show resolved Hide resolved
@BenediktBurger
Copy link
Member Author

Afterwards, we should check/update all open PRs for the new usage (adapter and name handling). I don't know how to meaningfully automate those checks.

Testing with something like

def test_args():
    adapter = FakeAdapter()
    instr = NewInstrument(adapter=adapter, **{'name': "test successful"})
    assert NewInstrument.name == "test successful"

This test verifies, whether the first argname is adapter, because if it is not adapter, it won't receive a value and will raise an error.
It also verifies, that the name argument is present somehow.

@bilderbuchi
Copy link
Member

This test verifies, whether the first argname is adapter, because if it is not adapter, it won't receive a value and will raise an error.
It also verifies, that the name argument is present somehow.

Sorry, I was unclear. Yeah such a test tests what we need, but it does not do that (dynamically) for any instruments changed in a PR. Adding this same test to all instruments individually seems wasteful. I think we could enumerate all instruments, and feed those to that test, though! (or one test for the adapter, one for the name) :-)

@BenediktBurger
Copy link
Member Author

Sorry, I was unclear. Yeah such a test tests what we need, but it does not do that (dynamically) for any instruments changed in a PR. Adding this same test to all instruments individually seems wasteful. I think we could enumerate all instruments, and feed those to that test, though! (or one test for the adapter, one for the name) :-)

I wrote a test, which searches for all instruments in pymeasure and does this test.
The problem is, however, that the inits of some devices fail, because they need some initial communication (adapter.connection.clear or setting/getting something etc.) or because they require some obligatory arguments (port, baud_rate...), which we do not supply.
Right now we pass 58 and fail 26.

@bilderbuchi
Copy link
Member

bilderbuchi commented Aug 3, 2022

I'd say push the test, if there are obvious fixes make them, if we need to add something to FakeAdapter (if only that some instrument has something to call) we can do that.
Finally, assuming you store the found instruments in a list for parameterize, we can make a filter list where we can skip (pytest.skipif or mark xfail with strict mode) an instrument test if it's on the list (separate for a test for name and a test for adapter?). That way we have 1-2 filter list(s) we can slowly work to reduce. What do you say?

@bilderbuchi
Copy link
Member

In general, that list of all instruments (module-level ALL_INSTRUMENTS?) could come in handy for further API tests like this. :-)

@bilderbuchi
Copy link
Member

Awesome addition with that test, we can work with that!

@msmttchr
Copy link
Member

msmttchr commented Aug 3, 2022

(This comment is related to
#659 (comment), but from mobile phone rendering I am not sure this obvious)
I am not entirely clear what is the advantage, I think the name of the instrument is something fixed and defined by instrument driver writer. Why do we want to allow user to override it ?
If user wants to do this, he can still do something like inst.name = "Something".
On the other hand, implementating this suggestion has triggered a lot of changes in all instruments making, in my opinion, the code more obscure

@BenediktBurger
Copy link
Member Author

I am not entirely clear what is the advantage, I think the name of the instrument is something fixed and defined by instrument driver writer. Why do we want to allow user to override it ? If user wants to do this, he can still do something like inst.name = "Something". On the other hand, implementating this suggestion has triggered a lot of changes in all instruments making, in my opinion, the code more obscure

For a user yes, instr.name = "xy" works, but for subclassing, you can just hand the name to the parent class as if it were Instrument itself.

@bilderbuchi
Copy link
Member

I am not entirely clear what is the advantage, I think the name of the instrument is something fixed and defined by instrument driver writer. Why do we want to allow user to override it ?

One use case for users giving custom names is when you have an experiment with several identical devices, e.g. several power supplies controlling coil current, bias voltage, excitation voltage (random example), then you would name the instances as such so that it's clearer/more convenient to see further down (e.g. in logging, GUI use, etc).
E.g. I have previously written code that uses the instrument name when naming a data file name or data column name.

The other use case is when subclassing, as already mentioned.

@BenediktBurger
Copy link
Member Author

With that, I think, we're good to go.

@bilderbuchi
Copy link
Member

With that, I think, we're good to go.

Me too.
@msmttchr, agree to merge this?

@BenediktBurger
Copy link
Member Author

Another example of issues with the name, see #698 (comment)

In order to ensure more easy subclassing, I'm for enabling all instruments to allow a name argument. This PR is already done, so I propse to use it.
If we decide to change to name="Some funny instrument" argument in the init, we still can do it without breaking anything. The work to do this change will be almost the same with or without this PR merged.
However, if you prefer, I would do the work to change all instruments to super().__init__(adapter, name="My name", **kwargs).

Is that PR #698 enough for you, to see name as changeable, @msmttchr?

If not, we can close the PR (the code still remains and could be used later on).
@bilderbuchi

@msmttchr
Copy link
Member

Another example of issues with the name, see [#698 (comment)](#698 (comment)

Is that PR #698 enough for you, to see name as changeable, @msmttchr?

Yes, even though I would do as class attribute as I mentioned previously. On the other hand, pretty much all users prefer to have it as parameter, so for me it's ok to have as parameter.

@BenediktBurger
Copy link
Member Author

Now remains the question of the documentation:

Do we want only one style (init parameter or kwargs.setdefault), do we allow both, but prefer one or do we allow both?
In the first two questions: which one?

@AidenDawn
Copy link
Contributor

Something I remember from #719 is that if ATSBase and Instrument now have a name during the test, they don't need to be skipped in test_all_instruments.py anymore. I've given this a quick test in your branch, but it would be worth double-checking

@bilderbuchi
Copy link
Member

Now remains the question of the documentation:

Do we want only one style (init parameter or kwargs.setdefault), do we allow both, but prefer one or do we allow both? In the first two questions: which one?

I have a slight preference for the "init parameter" way (as Casper said we don't have that much clutter in the signature especially when includeSCPI should disappear, and it's a bit clearer that this option is there).
The prefernce is not strong, so I would also be ok with the setdefault way if others prefer that.
Whatever we choose, we should harmonize on that one in the codebase and docs (which will effectively lead by example), but not forbid the other, imo

@BenediktBurger
Copy link
Member Author

BenediktBurger commented Nov 22, 2022

I prefer the init parameter as well, but I'm reluctant to touch all the instruments.

What do you think:

  1. We reduce this PR to make the documentation only (regarding init parameter).
  2. We introduce the SCPIMixin (without yet removing includeSCPI).
  3. When we switch all instruments from includeSCPI to a SCPIMixin, we have to touch all of them (without introducing any other code of discussion), such that we can do both (SCPI and init parameter) in one go.

EDIT:
correct super invocation in Keysight N7776, LakeShore421

@bilderbuchi
Copy link
Member

Excellent suggestion, agreed, that's better!

@BenediktBurger
Copy link
Member Author

Divide and conquer 😁

I have learnt my lesson of large PRs with too little talk about the principles first.

@BenediktBurger
Copy link
Member Author

As discussed, I stored the changes in a local branch (if we should need them) and started from scratch:

  • Documentation is changed to the new style
  • All instruments are tested for accepting a "name" parameter. All currently present instruments are exempt (for future rework). The scope of the tests is to force new instruments to use that scheme.

Should we add a change note? It only affects new instrument creators.

Copy link
Member

@bilderbuchi bilderbuchi left a comment

Choose a reason for hiding this comment

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

LGTM, with some restructuring suggestions for the tests.

tests/instruments/test_all_instruments.py Outdated Show resolved Hide resolved
tests/instruments/test_all_instruments.py Outdated Show resolved Hide resolved
tests/instruments/test_all_instruments.py Outdated Show resolved Hide resolved
tests/instruments/test_all_instruments.py Outdated Show resolved Hide resolved
@bilderbuchi
Copy link
Member

Should we add a change note? It only affects new instrument creators.

Why not. A hint about the new best practice could be useful.

When I compile the changelog, I mostly go by PR titles and what I remember from the PRs. If there are details that should or must be in the changelog, it's good to add them with the PR, at least for us maintainers -- there's less potential that something is missed. This primarily concerns "core"-touching PRs, for instruments it's straightforward enough most of the time.

@BenediktBurger
Copy link
Member Author

Thanks for the suggestions.

Rewording of variable names.
Change note added.
ATSBase has a default name.
@BenediktBurger
Copy link
Member Author

As you both had only these minor (unrelated) changes, we can merge, I think.

@msmttchr
Copy link
Member

As you both had only these minor (unrelated) changes, we can merge, I think.

I agree

Copy link
Member

@msmttchr msmttchr left a comment

Choose a reason for hiding this comment

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

Good for me

Copy link
Member

@bilderbuchi bilderbuchi left a comment

Choose a reason for hiding this comment

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

LGTM

@BenediktBurger BenediktBurger merged commit 4aea01d into pymeasure:master Nov 29, 2022
@BenediktBurger BenediktBurger deleted the fix-658 branch November 29, 2022 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Many instruments miss name argument
6 participants