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

ENH: add Client.apply method, adjust TaskStatus to properly capture exceptions #53

Merged
merged 8 commits into from
Jul 12, 2024

Conversation

tangkong
Copy link
Contributor

@tangkong tangkong commented Jul 9, 2024

Description

  • Adds Client.apply method, for writing values to the control system
  • Adjusts use of TaskStatus to actually capture exceptions, instead of propagating them

Motivation and Context

closes #7

Assorted thoughts

  • In the future it may be worth validating these Entries before applying them
  • TaskStatus was built to hold exceptions raised in the coroutines they wrap, but were propagating them to the calling context. This was caused by our used of asyncio.gather, which needed the return_exceptions=True kwarg. asyncio has various ways of handling and propagating exceptions, we just needed to choose the right one for us.

How Has This Been Tested?

Basic unit test added

Where Has This Been Documented?

This PR, docstrings etc

Pre-merge checklist

  • Code works interactively
  • Code follows the style guide
  • Code contains descriptive docstrings, including context and API
  • New/changed functions and methods are covered in the test suite where possible
  • Test suite passes locally
  • Test suite passes on GitHub Actions
  • Ran docs/pre-release-notes.sh and created a pre-release documentation page
  • Pre-release docs include context, functional descriptions, and contributors as appropriate

@tangkong tangkong requested review from ZLLentz and shilorigins July 10, 2024 19:33
for pv, data in zip(pv_list, data_list):
logger.debug(f'Putting {pv} = {data}')
status: TaskStatus = self.cl.put(pv, data)
if status.exception():
Copy link
Member

Choose a reason for hiding this comment

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

Is status guaranteed to be complete after self.cl.put returns 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.

It does currently, since we asyncio.run and block until the coroutine is finished. I'm not entirely convinced this is the best way to do things, but returning the status immediately would require us either

  • be in an async context
  • run the event loop in a separate thread (ophyd style).

I've been ok with this for now since we have the ability to run parallel puts (and wait on all of them), but it has been something I've been pondering changing

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 this is totally ok, I wanted to make sure you hadn't skip a wait call here (since wait was added in this PR).
It's not so strange to require more nuanced asynchronous behavior to be done in an async context.

return

if isinstance(entry, Setpoint):
return [self.cl.put(entry.pv_name, entry.data)]
Copy link
Member

Choose a reason for hiding this comment

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

Given the extra wrapping of list/not list here, I have to wonder if it would have been simpler for put to always return a list of statuses, even if we only put one PV

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 had this thought too. In the end I decided that if I, as a user of the Client, put to a single PV and had to index into a single-element list, I'd be annoyed.

But maybe the users and I can just deal with it 🤷

Copy link
Member

Choose a reason for hiding this comment

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

Either way is probably ok, your explanation here makes sense to me

@@ -57,6 +59,27 @@ def success(self) -> bool:
and self.task.exception() is None
)

def wait(self, timeout=None) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

Small documentation thing: you've added this wait function here and not documented its inclusion in the release notes or in the PR text. It's also not used anywhere outside the test suite yet.

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 thought I'd need it, then I realized the full ramifications of the statuses I'd created 😆

@tangkong tangkong self-assigned this Jul 11, 2024
superscore/client.py Outdated Show resolved Hide resolved
Co-authored-by: Devan Agrawal <[email protected]>
Copy link
Collaborator

@shilorigins shilorigins left a comment

Choose a reason for hiding this comment

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

I'm happy with this!

@tangkong
Copy link
Contributor Author

Alright, merging and moving along

@tangkong tangkong merged commit 7cc33ef into pcdshub:master Jul 12, 2024
11 checks passed
@tangkong tangkong added this to the Minimum Viable Product milestone Sep 9, 2024
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.

Write apply-Snapshot-to-EPICS routine
3 participants