Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoSign in
Socket

softioc

Package Overview
Dependencies
Maintainers
0
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

softioc - pypi Package Compare versions

Comparing version
4.0.2
to
4.1.0
+32
softioc/cothread_dispatcher.py
class CothreadDispatcher:
def __init__(self, dispatcher = None):
"""A dispatcher for `cothread` based IOCs, suitable to be passed to
`softioc.iocInit`. By default scheduled tasks are run on a dedicated
cothread callback thread, but an alternative dispatcher can optionally
be specified here. Realistically the only sensible alternative is to
pass `cothread.Spawn`, which would create a separate cothread for each
dispatched callback.
"""
if dispatcher is None:
# Import here to ensure we don't instantiate any of cothread's
# global state unless we have to
import cothread
# Create our own cothread callback queue so that our callbacks
# processing doesn't interfere with other callback processing.
self.__dispatcher = cothread.cothread._Callback()
else:
self.__dispatcher = dispatcher
def __call__(
self,
func,
func_args=(),
completion = None,
completion_args=()):
def wrapper():
func(*func_args)
if completion:
completion(*completion_args)
self.__dispatcher(wrapper)
+1
-1
Metadata-Version: 2.1
Name: softioc
Version: 4.0.2
Version: 4.1.0
Summary: Embed an EPICS IOC in a Python process

@@ -5,0 +5,0 @@ Home-page: https://github.com/dls-controls/pythonSoftIOC

@@ -61,3 +61,3 @@ [metadata]

addopts =
--tb=native -vv --doctest-modules --ignore=iocStats --ignore=epicscorelibs --ignore=docs
--tb=native -vv --doctest-modules --ignore=softioc/iocStats --ignore=epicscorelibs --ignore=docs
--cov=softioc --cov-report term --cov-report xml:cov.xml

@@ -64,0 +64,0 @@ asyncio_mode = auto

Metadata-Version: 2.1
Name: softioc
Version: 4.0.2
Version: 4.1.0
Summary: Embed an EPICS IOC in a Python process

@@ -5,0 +5,0 @@ Home-page: https://github.com/dls-controls/pythonSoftIOC

@@ -13,2 +13,3 @@ LICENSE

softioc/builder.py
softioc/cothread_dispatcher.py
softioc/device.dbd

@@ -15,0 +16,0 @@ softioc/device.py

@@ -11,4 +11,4 @@ # Compute a version number from a git repo or archive

# These will be filled in if git archive is run or by setup.py cmdclasses
GIT_REFS = 'tag: 4.0.2'
GIT_SHA1 = '1eb9405'
GIT_REFS = 'tag: 4.1.0'
GIT_SHA1 = '77ec950'

@@ -15,0 +15,0 @@ # Git describe gives us sha1, last version-like tag, and commits since then

@@ -11,6 +11,7 @@ import asyncio

`softioc.iocInit`. Means that `on_update` callback functions can be
async. If loop is None, will run an Event Loop in a thread when created.
async.
If a ``loop`` is provided it must already be running. Otherwise a new
Event Loop will be created and run in a dedicated thread.
"""
#: `asyncio` event loop that the callbacks will run under.
self.loop = loop
if loop is None:

@@ -30,11 +31,22 @@ # Make one and run it in a background thread

worker.start()
elif not loop.is_running():
raise ValueError("Provided asyncio event loop is not running")
else:
self.loop = loop
def __call__(self, func, *args):
def __call__(
self,
func,
func_args=(),
completion = None,
completion_args=()):
async def async_wrapper():
try:
ret = func(*args)
ret = func(*func_args)
if inspect.isawaitable(ret):
await ret
if completion:
completion(*completion_args)
except Exception:
logging.exception("Exception when awaiting callback")
logging.exception("Exception when running dispatched callback")
asyncio.run_coroutine_threadsafe(async_wrapper(), self.loop)

@@ -11,2 +11,4 @@ import os

from . import device, pythonSoftIoc # noqa
# Re-export this so users only have to import the builder
from .device import SetBlocking # noqa

@@ -305,3 +307,5 @@ PythonDevice = pythonSoftIoc.PythonDevice()

'LoadDatabase',
'SetDeviceName', 'UnsetDevice'
'SetDeviceName', 'UnsetDevice',
# Device support functions
'SetBlocking'
]

@@ -9,3 +9,9 @@ import os

from . import fields
from .imports import dbLoadDatabase, recGblResetAlarms, db_put_field
from .imports import (
create_callback_capsule,
dbLoadDatabase,
signal_processing_complete,
recGblResetAlarms,
db_put_field,
)
from .device_core import DeviceSupportCore, RecordLookup

@@ -15,6 +21,17 @@

# This is set from softioc.iocInit
# dispatcher(func, *args) will queue a callback to happen
dispatcher = None
# Global blocking flag, used to mark asynchronous (False) or synchronous (True)
# processing modes for Out records.
# Default False to maintain behaviour from previous versions.
blocking = False
# Set the current global blocking flag, and return the previous value.
def SetBlocking(new_val):
global blocking
old_val = blocking
blocking = new_val
return old_val
# EPICS processing return codes

@@ -147,2 +164,6 @@ EPICS_OK = 0

self._blocking = kargs.pop('blocking', blocking)
if self._blocking:
self._callback = create_callback_capsule()
self.__super.__init__(name, **kargs)

@@ -168,5 +189,14 @@

def __completion(self, record):
'''Signals that all on_update processing is finished'''
if self._blocking:
signal_processing_complete(record, self._callback)
def _process(self, record):
'''Processing suitable for output records. Performs immediate value
validation and asynchronous update notification.'''
if record.PACT:
return EPICS_OK
value = self._read_value(record)

@@ -190,3 +220,9 @@ if not self.__always_update and \

if self.__on_update and self.__enable_write:
dispatcher(self.__on_update, python_value)
record.PACT = self._blocking
dispatcher(
self.__on_update,
func_args=(python_value,),
completion = self.__completion,
completion_args=(record,))
return EPICS_OK

@@ -193,0 +229,0 @@

@@ -1,2 +0,2 @@

/* Provide EPICS functions in Python format */
#define PY_SSIZE_T_CLEAN

@@ -9,2 +9,3 @@ #include <Python.h>

#include <dbFldTypes.h>
#include <callback.h>
#include <dbStaticLib.h>

@@ -17,2 +18,6 @@ #include <asTrapWrite.h>

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Field access helper functions. */
/* Reference stealing version of PyDict_SetItemString */

@@ -115,4 +120,3 @@ static void set_dict_item_steal(

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* IOC PV put logging */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* IOC PV put logging */

@@ -125,2 +129,3 @@ struct formatted

static struct formatted * FormatValue(struct dbAddr *dbaddr)

@@ -161,2 +166,3 @@ {

static void PrintValue(struct formatted *formatted)

@@ -178,2 +184,3 @@ {

void EpicsPvPutHook(struct asTrapWriteMessage *pmessage, int after)

@@ -218,2 +225,45 @@ {

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process callback support. */
#define CAPSULE_NAME "ProcessDeviceSupportOut.callback"
static void capsule_destructor(PyObject *obj)
{
free(PyCapsule_GetPointer(obj, CAPSULE_NAME));
}
static PyObject *create_callback_capsule(PyObject *self, PyObject *args)
{
void *callback = malloc(sizeof(CALLBACK));
return PyCapsule_New(callback, CAPSULE_NAME, &capsule_destructor);
}
static PyObject *signal_processing_complete(PyObject *self, PyObject *args)
{
int priority;
dbCommon *record;
PyObject *callback_capsule;
if (!PyArg_ParseTuple(args, "inO", &priority, &record, &callback_capsule))
return NULL;
if (!PyCapsule_IsValid(callback_capsule, CAPSULE_NAME))
return PyErr_Format(
PyExc_TypeError,
"Given object was not a capsule with name \"%s\"",
CAPSULE_NAME);
CALLBACK *callback = PyCapsule_GetPointer(callback_capsule, CAPSULE_NAME);
callbackRequestProcessCallback(callback, priority, record);
Py_RETURN_NONE;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Initialisation. */
static struct PyMethodDef softioc_methods[] = {

@@ -228,2 +278,6 @@ {"get_DBF_values", get_DBF_values, METH_VARARGS,

"Install caput logging to stdout"},
{"signal_processing_complete", signal_processing_complete, METH_VARARGS,
"Inform EPICS that asynchronous record processing has completed"},
{"create_callback_capsule", create_callback_capsule, METH_VARARGS,
"Create a CALLBACK structure inside a PyCapsule"},
{NULL, NULL, 0, NULL} /* Sentinel */

@@ -241,2 +295,3 @@ };

PyObject *PyInit__extension(void)

@@ -243,0 +298,0 @@ {

@@ -30,2 +30,12 @@ '''External DLL imports used for implementing Python EPICS device support.

def create_callback_capsule():
return _extension.create_callback_capsule()
def signal_processing_complete(record, callback):
'''Signal that asynchronous record processing has completed'''
_extension.signal_processing_complete(
record.PRIO,
record.record.value,
callback)
def expect_success(status, function, args):

@@ -98,2 +108,4 @@ assert status == 0, 'Expected success'

'get_field_offsets',
'create_callback_capsule',
'signal_processing_complete',
'registryDeviceSupportAdd',

@@ -100,0 +112,0 @@ 'IOSCANPVT', 'scanIoRequest', 'scanIoInit',

@@ -27,3 +27,3 @@ '''Device support files for pythonSoftIoc Python EPICS device support.

'on_update', 'on_update_name', 'validate', 'always_update',
'initial_value', '_wf_nelm', '_wf_dtype']
'initial_value', '_wf_nelm', '_wf_dtype', 'blocking']
device_kargs = {}

@@ -30,0 +30,0 @@ for keyword in DeviceKeywords:

@@ -10,2 +10,3 @@ import os

from . import imports, device
from . import cothread_dispatcher

@@ -35,6 +36,3 @@ __all__ = ['dbLoadDatabase', 'iocInit', 'interactive_ioc']

# Fallback to cothread
import cothread
# Create our own cothread callback queue so that our callbacks
# processing doesn't interfere with other callback processing.
dispatcher = cothread.cothread._Callback()
dispatcher = cothread_dispatcher.CothreadDispatcher()
# Set the dispatcher for record processing callbacks

@@ -41,0 +39,0 @@ device.dispatcher = dispatcher