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

Add additional callbacks for SharedPV handlers #155

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

Monarda
Copy link

@Monarda Monarda commented Aug 25, 2024

This PR adds three new callback functions for use by SharedPV (p4p.server.raw) handlers. They are:

  • open() - called when a SharedPV is opened
  • post() - called each time a post operation is performed on a SharedPV
  • close() - called when a SharedPV is closed.

The associated changes to the p4p.server.raw.Handler class, additional new decorators, and unit tests (src/p4p/test/test_sharedpv.py) are included. The changes to src/p4p/server/raw.py are relatively minor but the examples and unit tests make this look like a much bigger PR! There was some initial discussion of the idea at #150.

Three examples that make use of the new callback functions are also included:

  • example/auditor.py - demonstrates using open() and close() callbacks along with the existing put() callback to record when an auditing PV that records who's made changes to other PVs is or isn't available. Probably redundant compared to the more complex example/persist.py.
  • example/ntscalar_control.py - implements the Control field logic for an NTScalar. This illustrates how put(), post(), and open() lead to a natural separation of concerns. Logic that does not depend on the previous state of the PV may be implemented in the handler open(), e.g. altering the value dependent on control.limitHigh and control.limitLow. Logic that depends on the current state of the PV and the proposed changes may be implemented in the post(), e.g. applying control.minStep. The put() may then be solely concerned with authorisation.
  • example/persist.py - demonstrates using an SQLite3 database to automatically save and restore the values of PVs. Makes use of the new handler_open_ and handler_post_ prefix arguments to make configuration convenient and to transmit information from a put() to a post() respectively. Makes full use of the new open() callback to automatically restore the value and timeStamp information of a PV and post() to automatically record this information each time it changes.

I believe the example/persist.py file probably makes the strongest case for the kind of new features that these callbacks allow or make easier.

Breaking Changes

I believe these changes may be considered largely backwards compatible. They will only cause compatibility issues:

  • if an existing handler implements open(), close(), or post() functions.
  • if a PV includes top-level fields names beginning with handler_open_ or handler_post_.

@@ -154,10 +178,23 @@ def open(self, value, nt=None, wrap=None, unwrap=None, **kws):
self._wrap = wrap or (nt and nt.wrap) or self._wrap
self._unwrap = unwrap or (nt and nt.unwrap) or self._unwrap

# Intercept all arguments that start with 'handler_open_' and remove them from
# the arguments that go to the wrap and send them instead to the handler.open()
post_kws = {x: kws.pop(x) for x in [y for y in kws if y.startswith("handler_open_")]}
Copy link
Author

Choose a reason for hiding this comment

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

This, and the equivalent line 212 for post(), is probably one of the more important changes. It means that any arguments prefixed handler_open_ will be passed to the handler (if it exists) and not to _wrap(). An earlier version of the code added a single new argument that enabled or disabled the handler altogether. But while a smaller change it was also much crude.

@property
def onFirstConnect(self):
def on_open(self):
Copy link
Author

Choose a reason for hiding this comment

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

This is a leftover from an earlier version in which I thought I'd needed to supply decorator names that wouldn't clash with the function names. It does make the names PEP8 compliant so I've left the change in for consideration.

# Aliases for decorators to maintain consistent new style
# Required because post is already used and on_post seemed the best
# alternative.
put = on_put
Copy link
Author

Choose a reason for hiding this comment

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

Compatibility for previous decorator names.

self._handler.post(self, V, **post_kws)
except AttributeError:
pass

_SharedPV.post(self, V)
Copy link
Author

Choose a reason for hiding this comment

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

Implementing the persist.py example has made me think there may be a case to split the post() callback into two. The one implemented in this PR that changes values before they reach this _SharedPV.post(self, V) and one which is called afterwards. The after_post() callback would then only be supplied the updated state of the PV and would not be triggered by unsuccessful posts.

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.

1 participant