Exemple #1
0
def test_from_cfg(happi_cfg: str):
    # happi_cfg modifies environment variables to make config discoverable
    client = Client.from_config()
    # Internal db path should be constructed relative to the happi cfg dir
    expected_db = os.path.abspath(
        os.path.join(os.path.dirname(happi_cfg), 'db.json'))
    print(happi_cfg)
    assert isinstance(client.backend, JSONBackend)
    assert client.backend.path == expected_db
Exemple #2
0
 def add_connection(self, channel):
     # If we haven't made a Client by the time we need the Plugin. Try
     # and load one from configuration file
     if not _client:
         register_client(Client.from_config())
     try:
         super().add_connection(channel)
     except SearchError:
         logger.error("Unable to find device for %r in happi database.",
                      channel)
     except Exception:
         logger.exception("Unable to load %r from happi", channel.address)
def load_devices_from_happi(device_names, *, namespace, **kwargs):
    """
    Load the devices from Happi based on the list of ``device_names``. The elements of the list
    may be strings (device name) or tuples of two strings (device name used for database search and
    the name with which the device is loaded in the script namespace). Two names may be needed
    in case the devices are stored in the database using compound names that contain beamline
    acronyms (e.g. ``abc_det`` and ``def_det`` for beamlines ABC and DEF) but they are expected
    to have the name ``det`` when loaded during ABC and DEF beamline startup respectively (see examples).

    The devices are loaded into a namespace referenced by ``namespace`` parameter.
    The function may be called multiple times in a row for the same namespace to populate
    it with results of multiple searches. The function also returns the list of names of loaded devices.

    Happi should be configured before the function could be used: Happi configuration file should be
    created and environment variable ``HAPPI_CFG`` with the path to the configuration file should be set.
    For example, if JSON Happi database is contained in the file ``path=/home/user/happi/database.json``
    and the path to configuration file is ``path=/home/user/happi.ini``, then the environment variable
    should be set as

    .. code-block::

        HAPPI_CFG=/home/user/happi.ini

    and configuration file ``happi.ini`` should contain

    .. code-block::

        [DEFAULT]
        backend=json
        path=/home/user/happi/database.json

    Examples
    --------

    .. code-block:: python

        # Load devices 'det1' and 'motor1'.
        load_devices_from_happi(["det1", "motor1"], namespace=locals())
        # Works exactly the same as the previous example. If the second tuple element is
        #   evaluated as boolean value of False, then it is ignored and the device is not renamed.
        load_devices_from_happi([("det1", ""), ("motor1", "")], namespace=locals())

        # Load 'abc_det1' as 'det1' and 'abc_motor1' as 'motor1'.
        load_devices_from_happi([("abc_det1", "det1"), ("abc_motor1", "motor1")], namespace=locals())

        # Get the dictionary of devices
        device_dict = {}
        load_devices_from_happi(["det1", "motor1"], namespace=device_dict)
        # 'device_dict': {"dev1": device1, "motor1": motor1}

        # Obtain the list of updated items (devices)
        item_list = load_devices_from_happi([("abc_det1", "det1"), ("abc_motor1", "motor1")], namespace=locals())
        # 'item_list': ["det1", "motor1"]

    Parameters
    ----------
    device_names : list(str) or list(tuple(str))
        List of device names. Elements of the list could be strings or tuples (lists) of string with
        two elements. If an element is a tuple of two names, then the first name is used in database
        search and the second name is the name of the device after it is loaded into the namespace.
        Each element of the list is processed separately and may contain mix of strings and tuples.
        If the second name in the tuple is an empty string, then it is ignored and
        the device is imported with the same name as used in database. It is expected that search
        will result in one found device per device name, otherwise ``RuntimeError`` is raised.
    namespace : dict
        Reference to the namespace where the devices should be loaded. It can be the reference
        to ``global()`` or ``locals()`` of the startup script or reference to a dictionary.
        It is a required keyword argument.
    kwargs : dict
        Additional search parameters.

    Returns
    -------
    list(str)
        the list of names of items in the ``namespace`` that were updated by the function.

    Raises
    ------
    RuntimeError
        Search for one of the device names returns no devices or more than one device.
    """

    kwargs = kwargs or {}

    if not isinstance(namespace, dict):
        raise TypeError(
            f"Parameter 'namespace' must be a dictionary: the value of type {type(namespace)} was passed instead"
        )

    # Verify that 'device_names' has correct type
    if not isinstance(device_names, (tuple, list)):
        raise TypeError(
            "Parameter 'device_names' value must be a tuple or a list: "
            f"type(device_names) = {type(device_names)}")
    for n, name in enumerate(device_names):
        if not isinstance(name, (str, tuple, list)):
            raise TypeError(
                f"Parameter 'device_names': element #{n} must be str, tuple or list: "
                f"device_names[n] = {name}")
        if isinstance(name, (tuple, list)):
            if len(name) != 2 or not isinstance(
                    name[0], str) or not isinstance(name[1], str):
                raise TypeError(
                    f"Parameter 'device_names': element #{n} is expected to be in the form "
                    f"('name_in_db', 'name_in_namespace'): device_names[n] = {name}"
                )
            elif name[1] and not re.search(r"^[a-z][_a-z0-9]*$", name[1]):
                raise TypeError(
                    f"The device '{name[0]}' can not be renamed: The new device name '{name[1]}' "
                    "may consist of lowercase letters, numbers and '_' and must start from lowercase letter"
                )

    from happi import Client, load_devices

    client = Client.from_config()

    results = []
    for d_name in device_names:
        if isinstance(d_name, str):
            name_db, name_ns = d_name, None
        else:
            name_db, name_ns = d_name

        # Assemble search parameters
        search_params = dict(kwargs)
        search_params.update({"name": name_db})

        res = client.search(**search_params)
        if not res:
            raise RuntimeError(
                f"No devices with name '{name_db}' were found in Happi database. "
                f"Search parameters {search_params}")
        elif len(res) > 1:
            raise RuntimeError(
                f"Multiple devices with name '{name_db}' were found in Happi database. "
                f"Search parameters {search_params}")
        else:
            r = res[0]
            # Modify the object name (if needed)
            if name_ns:
                # In order for the following conversion to work properly, the name of
                #   the device should be specified only once (as `name` attribute),
                #   and aliases `{{name}}` should be used if the name is used as any other
                #   parameter. This is standard and recommended practice for instantiating
                #   Happi items (devices).
                #
                # In search results, the reference to the device is `r._device`.
                # `r.metadata` contains expanded metadata (it is probably not used, but it
                #   is a good idea to change it as well for consistency. We don't touch `_id`.
                # The modified data is not expected to be saved to the database.
                setattr(r._device, "name", name_ns)
                r.metadata["name"] = name_ns
            # Instantiate the object
            results.append(r)

    ns = load_devices(*[_.item for _ in results])
    ns_dict = ns.__dict__
    namespace.update(ns_dict)
    return list(ns_dict)
Exemple #4
0
def test_from_cfg_abs(happi_cfg_abs: str):
    # happi_cfg modifies environment variables to make config discoverable
    client = Client.from_config()
    assert isinstance(client.backend, JSONBackend)
    # Ensure the json backend is using the db that we gave an absolute path to.
    assert client.backend.path == '/var/run/db.json'
Exemple #5
0
def test_from_cfg(happi_cfg):
    client = Client.from_config()
    assert isinstance(client.backend, JSONBackend)
    assert client.backend.path == 'db.json'