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

New signal typing #594

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open

New signal typing #594

wants to merge 33 commits into from

Conversation

coretl
Copy link
Collaborator

@coretl coretl commented Sep 24, 2024

Rewrite the type support, and use a plugin Connector architecture to support reconnection for PVI and Tango.

Fixes #472, fixes #562, fixes #535, fixes #505, fixes #373, fixes #601

Required for #551

Breaking changes

pvi structure changes

Structure now read from .value rather than .pvi. Supported in FastCS. Requires at least PandABlocks-ioc 0.10.0

StrictEnum is now requried for all strictly checked Enums

# old
from enum import Enum
class MyEnum(str, Enum):
    ONE = "one"
    TWO = "two"
# new
from ophyd_async.core import StrictEnum
class MyEnum(StrictEnum):
    ONE = "one"
    TWO = "two"

SubsetEnum is now an Enum subclass:

from ophyd_async.core import SubsetEnum
# old
MySubsetEnum = SubsetEnum["one", "two"]
# new
class MySubsetEnum(SubsetEnum):
    ONE = "one"
    TWO = "two"

Use python primitives for scalar types instead of numpy types

# old
import numpy as np
x = epics_signal_rw(np.int32, "PV")
# new
x = epics_signal_rw(int, "PV")

Use Array1D for 1D arrays instead of npt.NDArray

import numpy as np
# old
import numpy.typing as npt
x = epics_signal_rw(npt.NDArray[np.int32], "PV")
# new
from ophyd_async.core import Array1D
x = epics_signal_rw(Array1D[np.int32], "PV")

Use Sequence[str] for arrays of strings instead of npt.NDArray[np.str_]

import numpy as np
# old
import numpy.typing as npt
x = epics_signal_rw(npt.NDArray[np.str_], "PV")
# new
from collections.abc import Sequence
x = epics_signal_rw(Sequence[str], "PV")

MockSignalBackend requires a real backend

# old
fake_set_signal = SignalRW(MockSignalBackend(float))
# new
fake_set_signal = soft_signal_rw(float)
await fake_set_signal.connect(mock=True)

get_mock_put is no longer passed timeout as it is handled in Signal

# old
get_mock_put(driver.capture).assert_called_once_with(Writing.ON, wait=ANY, timeout=ANY)
# new
get_mock_put(driver.capture).assert_called_once_with(Writing.ON, wait=ANY)

super().__init__ required for Device subclasses

# old
class MyDevice(Device):
    def __init__(self, name: str = ""):
        self.signal, self.backend_put = soft_signal_r_and_setter(int)
# new
class MyDevice(Device):
    def __init__(self, name: str = ""):
        self.signal, self.backend_put = soft_signal_r_and_setter(int)
        super().__init__(name=name)

Arbitrary BaseModels not supported, pending use cases for them

The Table type has been suitable for everything we have seen so far, if you need an arbitrary BaseModel subclass then please make an issue

Child Devices set parent on attach, and can't be public children of more than one parent

class SourceDevice(Device):
    def __init__(self, name: str = ""):
        self.signal = soft_signal_rw(int)
        super().__init__(name=name)

# old
class ReferenceDevice(Device):
    def __init__(self, signal: SignalRW[int], name: str = ""):
        self.signal = signal
        super().__init__(name=name)

    def set(self, value) -> AsyncStatus:
        return self.signal.set(value + 1)
# new
class ReferenceDevice(Device):
    def __init__(self, signal: SignalRW[int], name: str = ""):
        self._signal = signal
        super().__init__(name=name)

    def set(self, value) -> AsyncStatus:
        return self._signal.set(value + 1)

@coretl
Copy link
Collaborator Author

coretl commented Sep 24, 2024

Apologies, another unreviewable PR, but if you start from the tests you can see the changes needed

Copy link
Contributor

@evalott100 evalott100 left a comment

Choose a reason for hiding this comment

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

Most of these are discussion points more than "requested changes". I definitely like abstracting out connection logic.

pyproject.toml Show resolved Hide resolved
pyproject.toml Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_soft_signal_backend.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_table.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_utils.py Outdated Show resolved Hide resolved
src/ophyd_async/epics/signal/_signal.py Outdated Show resolved Hide resolved
src/ophyd_async/epics/signal/_signal.py Outdated Show resolved Hide resolved
Copy link
Contributor

@evalott100 evalott100 left a comment

Choose a reason for hiding this comment

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

I played around with trying to find the nicest way to default type connector for wayyyy to long 😅

src/ophyd_async/core/_signal_backend.py Show resolved Hide resolved
src/ophyd_async/core/_table.py Outdated Show resolved Hide resolved
@coretl
Copy link
Collaborator Author

coretl commented Oct 11, 2024

@evalott100 if you get a chance please could you review core._device.py and core._signal.py

src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
@burkeds
Copy link
Contributor

burkeds commented Oct 21, 2024

import numpy as np
# old
import numpy.typing as npt
x = epics_signal_rw(npt.NDArray[np.int32], "PV")
# new
from ophyd_async.core import Array1D
x = epics_signal_rw(Array1D[np.int32], "PV")

@coretl I suspect this will break type enforcement on the tango backend. PyTango will read in non-scalar values as numpy arrays. This change will necessitate a rewrite of get_trl_descriptor. I really don't like this method. I think I want to refactor it and put its type enforcement elsewhere. What do you think?

Copy link
Contributor

@evalott100 evalott100 left a comment

Choose a reason for hiding this comment

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

Will examine further tomorrow.

"event_model",
"p4p",
"bluesky @ git+https://github.com/bluesky/bluesky@main",
"event-model @ git+https://github.com/bluesky/event-model@main",
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll do a patch release of event model soon which allows |

https://github.com/bluesky/ophyd-async/pull/594/files#r1784069033

src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_device.py Show resolved Hide resolved
src/ophyd_async/core/_device.py Outdated Show resolved Hide resolved
src/ophyd_async/core/_readable.py Show resolved Hide resolved
src/ophyd_async/core/_readable.py Show resolved Hide resolved
src/ophyd_async/core/_signal.py Outdated Show resolved Hide resolved
@rtuck99
Copy link
Contributor

rtuck99 commented Oct 22, 2024

Seems like assert_configuration() (and presumably also assert_reading()) are broken for numpy array types. I guess they probably were already broken before these changes, but one of the tests in dodal uses assert_configuration and I encountered this as I'm currently in the process of going through dodal and mx-bluesky to test out this PR.
I will keep you posted if I find any other issues.

@Tom-Willemsen
Copy link
Member

Just tried this branch on our devices at ISIS, I'm getting a timeout when doing trigger on a device, despite explicitly specifying no timeout.

Problematic line is something like:

await dae.controls.end_run.trigger(wait=True, timeout=None)

where the relevant signal will give an EPICS completion callback eventually, but can be quite slow (~30s). My understanding was that specifying timeout=None should explicitly say "no timeout", and this seemed to work previously, but on this branch I'm seeing a timeout error. Full stack trace listed in this PR.

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