Exemple #1
0
def test_create_item(happi_client: Client, item_info: Dict[str, Any]):
    item = happi_client.create_item(OphydItem, **item_info)
    assert item.prefix == item_info['prefix']
    assert item.name == item_info['name']
    # Invalid Entry
    with pytest.raises(TypeError):
        happi_client.create_item(int)
Exemple #2
0
def test_choices_for_field(happi_client: Client):
    name_choices = happi_client.choices_for_field('name')
    assert name_choices == {'alias'}
    prefix_choices = happi_client.choices_for_field('prefix')
    assert prefix_choices == {'BASE:PV'}
    with pytest.raises(SearchError):
        happi_client.choices_for_field('not_a_field')
Exemple #3
0
def test_validate(happi_client: Client):
    # No bad items
    assert happi_client.validate() == list()
    # A single bad item
    happi_client.backend.save('_id', {happi_client._id_key: 'bad'},
                              insert=True)
    assert happi_client.validate() == ['bad']
Exemple #4
0
    def __init__(self):
        self._happi_db_dirs = [happi_site_dir, happi_user_dir]
        self._device_view = HappiClientTreeView()
        self._client_model = HappiClientModel()
        self._device_view.setModel(self._client_model)
        for db_dir in self._happi_db_dirs:
            for db_file in Path(db_dir).glob('*.json'):
                client = Client(path=str(db_file))
                self._client_model.add_client(client)
        try:
            mongo_client = Client(
                MongoBackend(host='127.0.0.1',
                             db='happi',
                             collection='labview_static',
                             user=USER_MONGO,
                             pw=PW_MONGO,
                             timeout=None))
            self._client_model.add_client(mongo_client)
        except Exception as e:  #TODO catch exception properly
            msg.logError(e)

        widget = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(self._device_view)
        widget.setLayout(layout)

        icon = QIcon(str(static.path('icons/calibrate.png')))
        name = "Devices"
        super(HappiSettingsPlugin, self).__init__(icon, name, widget)
        self._device_view.expandAll()
        self.restore()
Exemple #5
0
def test_export(happi_client: Client, valve: OphydItem):
    # Setup client with both items
    happi_client.add_item(valve)
    fd, temp_path = tempfile.mkstemp()
    happi_client.export(open(temp_path, 'w+'),
                        sep=',',
                        attrs=['name', 'prefix'])
    exp = open(temp_path, 'r').read()
    assert "alias,BASE:PV" in exp
    assert "name,BASE:VGC:PV" in exp
    # Cleanup
    os.remove(temp_path)
    os.close(fd)
Exemple #6
0
def test_find_document(happi_client: Client, item_info: Dict[str, Any]):
    doc = happi_client.find_document(**item_info)
    assert doc.pop('prefix') == item_info['prefix']
    assert doc.pop('name') == item_info['name']
    # Remove id and check
    prefix = item_info.pop('prefix')
    doc = happi_client.find_document(**item_info)

    assert doc.pop('prefix') == prefix
    assert doc.pop('name') == item_info['name']
    # Not available
    with pytest.raises(SearchError):
        doc = happi_client.find_document(prefix='Does not Exist')
Exemple #7
0
def test_change_item_name(happi_client: Client, item_info: Dict[str, Any]):
    item = happi_client.find_item(**item_info)
    assert item.name != 'new_name'
    item.name = 'new_name'
    item.save()
    # old entry should not exist anymore
    with pytest.raises(SearchError):
        happi_client.find_item(**item_info)
    # new entry with new name should be there
    new_item = happi_client.find_item(name=item.name)
    # prefix or other attributes should be the same
    assert new_item.prefix == item.prefix
    # we should only have one deivce now which is the new one
    assert happi_client.all_items == [new_item]
Exemple #8
0
def mockjsonclient():
    # Write underlying database
    with open('testing.json', 'w+') as handle:
        simplejson.dump({device_info()['prefix']: device_info()}, handle)
    # Return handle name
    db = JSONBackend('testing.json')
    return Client(database=db)
Exemple #9
0
def test_find_item(happi_client: Client, item_info: Dict[str, Any]):
    item = happi_client.find_item(**item_info)
    assert isinstance(item, OphydItem)
    assert item.prefix == item_info['prefix']
    assert item.name == item_info['name']
    # Test edit and save
    item.stand = 'DG3'
    item.save()
    loaded_item = happi_client.find_item(**item_info)
    assert loaded_item.prefix == item_info['prefix']
    assert loaded_item.name == item_info['name']
    # Bad load
    bad = {'a': 'b'}
    happi_client.backend.save('a', bad, insert=True)
    with pytest.raises(EntryError):
        happi_client.find_item(**bad)
Exemple #10
0
def test_beckoff_axis_device_class(mockqsbackend):
    c = Client(database=mockqsbackend)
    d = load_devices(*c.all_items).__dict__
    vh_y = d.get('vh_y')
    sam_x = d.get('sam_x')
    assert vh_y.__class__.__name__ == 'BeckhoffAxis'
    assert sam_x.__class__.__name__ == 'IMS'
Exemple #11
0
def test_qsbackend_with_acromag(mockqsbackend):
    c = Client(database=mockqsbackend)
    d = load_devices(*c.all_devices, pprint=False).__dict__
    ai1 = d.get('ai_7')
    ao1 = d.get('ao_6')
    assert ai1.__class__.__name__ == 'EpicsSignalRO'
    assert ao1.__class__.__name__ == 'EpicsSignal'
Exemple #12
0
def mockjsonclient(item_info: Dict[str, Any]):
    # Write underlying database
    with tempfile.NamedTemporaryFile(mode='w') as handle:
        simplejson.dump({item_info['name']: item_info}, handle)
        handle.flush()  # flush buffer to write file
        # Return handle name
        db = JSONBackend(handle.name)
        yield Client(database=db)
Exemple #13
0
def test_change_container_pass(happi_client: Client, item: str, target: str,
                               request: pytest.FixtureRequest):
    i = request.getfixturevalue(item)
    t = request.getfixturevalue(target)
    kw = happi_client.change_container(i, t)

    for k in kw:
        assert i.post()[k] == kw[k]
Exemple #14
0
    def __init__(self,
                 prefix='',
                 *,
                 name,
                 client: happi.Client,
                 devices: List[str]):
        super(CompoundDevice, self).__init__(prefix=prefix, name=name)

        self.devices = client.find_device()
Exemple #15
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 #16
0
def test_qsbackend_with_client(mockqsbackend):
    c = Client(database=mockqsbackend)
    assert len(c.all_items) == 14
    assert all(
        d.__class__.__name__ in {'Trigger', 'Motor', 'Acromag', 'LCLSItem'}
        for d in c.all_items)
    device_types = [device.__class__.__name__ for device in c.all_items]
    assert device_types.count('Motor') == 7
    assert device_types.count('Trigger') == 2
    # Acromag: six entries, but one erroneously has the same name
    assert device_types.count('Acromag') == 5
Exemple #17
0
def mockmongoclient(item_info: Dict[str, Any]):
    with patch('happi.backends.mongo_db.MongoClient') as mock_mongo:
        mc = MongoClient()
        mc['test_db'].create_collection('test_collect')
        mock_mongo.return_value = mc
        # Client
        backend = MongoBackend(db='test_db', collection='test_collect')
        client = Client(database=backend)
        # Insert a single device
        client.backend._collection.insert_one(item_info)
        return client
Exemple #18
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)
Exemple #19
0
def test_qsbackend_with_client(mockqsbackend):
    from pcdsdevices.device_types import Motor, Trigger, Acromag

    c = Client(database=mockqsbackend)
    assert len(c.all_devices) == 14
    assert all([
        isinstance(d, Motor) or isinstance(d, Trigger)
        or isinstance(d, Acromag) for d in c.all_devices
    ])
    device_types = [device.__class__ for device in c.all_devices]
    assert device_types.count(Motor) == 6
    assert device_types.count(Trigger) == 2
    assert device_types.count(Acromag) == 6
def make_tile_configs(tile, directory, dbpath):
    """
    Write TILE IOC configs based on database entries.
    """
    # Hack in our test device types
    registry._registry['pcdsdevices.Elliptec'] = Elliptec
    registry._reverse_registry[Elliptec] = 'pcdsdevices.Elliptec'
    registry._registry['pcdsdevices.Qmini'] = Qmini
    registry._reverse_registry[Qmini] = 'pcdsdevices.Qmini'
    registry._registry['pcdsdevices.SmarActMotor'] = SmarActMotor
    registry._reverse_registry[SmarActMotor] = 'pcdsdevices.SmarActMotor'
    registry._registry['pcdsdevices.SmarActTipTiltMotor'] = SmarActTipTiltMotor
    registry._reverse_registry[SmarActTipTiltMotor] = \
        'pcdsdevices.SmarActTipTiltMotor'
    registry._registry['pcdsdevices.LasBasler'] = LasBasler
    registry._reverse_registry[LasBasler] = 'pcdsdevices.LasBasler'
    registry._registry['pcdsdevices.ThorlabsWfs'] = ThorlabsWfs
    registry._reverse_registry[ThorlabsWfs] = 'pcdsdevices.ThorlabsWfs'
    registry._registry['pcdsdevices.ThorlabsPM101PowerMeter'] = \
        ThorlabsPM101PowerMeter
    registry._reverse_registry[ThorlabsPM101PowerMeter] = \
        'pcdsdevices.ThorlabsPM101PowerMeter'
    registry._registry['pcdsdevices.EnvironmentalMontior'] = \
        EnvironmentalMonitor
    registry._reverse_registry[EnvironmentalMonitor] = \
        'pcdsdevices.EnvironmentalMonitor'

    # Just use our test devices for now
    if tile not in ['lm1k4_com']:
        raise ValueError("Unrecognized TILE {}".format(tile))

    client = Client(path=dbpath)
    results = client.search(location_group=tile)
    for dev in dev_map.keys():  # Aggregate devices based on container type
        devs = [result for result in results if result['type'] == dev]
        print("Found {} devices of type {}".format(len(devs), dev))
        func = dev_map[devs[0]['type']]
        func(devs, directory)
Exemple #21
0
    def add_client(self, client: Client):
        self._clients.append(client)
        if isinstance(client.backend, JSONBackend):
            client_item = QStandardItem(client.backend.path)

        elif isinstance(client.backend, MongoBackend):
            client_item = QStandardItem(
                f"{client.backend._client.HOST}/{client.backend._collection.full_name}"
            )
        self.appendRow(client_item)
        for result in client.search():
            # add an OphydItem
            self.add_device(client_item, result.item)
        client_item.setData(client)
Exemple #22
0
def test_add_item(happi_client: Client, valve: OphydItem):
    happi_client.add_item(valve)
    # No duplicates
    with pytest.raises(DuplicateError):
        happi_client.add_item(valve)
    # No incompletes
    d = OphydItem()
    with pytest.raises(EntryError):
        happi_client.add_item(d)
Exemple #23
0
def test_remove_item(happi_client: Client, item: OphydItem, valve: OphydItem,
                     item_info: Dict[str, Any]):
    happi_client.remove_item(item)
    assert list(happi_client.backend.find(item_info)) == []
    # Invalid item
    with pytest.raises(ValueError):
        happi_client.remove_item(5)
    # Valve not in dictionary
    with pytest.raises(SearchError):
        happi_client.remove_item(valve)
Exemple #24
0
def test_search_range(happi_client: Client, valve: OphydItem):
    happi_client.add_item(valve)
    # Search between two points
    res = happi_client.search_range('z', start=0, end=500)
    assert len(res) == 2
    # Search between two points, nothing found
    res = happi_client.search_range('z', start=10000, end=500000)
    assert not res
    # Search without an endpoint
    res = happi_client.search_range('z', start=0)
    assert len(res) == 2
    # Search invalid range
    with pytest.raises(ValueError):
        happi_client.search_range('z', start=1000, end=5)
Exemple #25
0
def test_search(happi_client: Client, valve: OphydItem, item_info: Dict[str,
                                                                        Any]):
    happi_client.add_item(valve)
    res = happi_client.search(name=item_info['name'])
    # Single search return
    assert len(res) == 1
    loaded_item = res[0].item
    assert loaded_item.prefix == item_info['prefix']
    assert loaded_item.name == item_info['name']
    # No results
    assert not happi_client.search(name='not')
    # Returned as dict
    res = happi_client.search(**item_info)
    loaded_item = res[0].item
    assert loaded_item['prefix'] == item_info['prefix']
    assert loaded_item['name'] == item_info['name']
    # Search off keyword
    res = happi_client.search(beamline='LCLS')
    assert len(res) == 2
Exemple #26
0
def client():
    client = Client(path=os.path.join(os.path.dirname(__file__), 'happi.json'))
    register_client(client)
    return client
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 #28
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 #29
0
def test_qsbackend_with_client(mockqsbackend):
    c = Client(database=mockqsbackend)
    assert len(c.all_devices) == 6
    assert all([isinstance(d, Motor) for d in c.all_devices])
Exemple #30
0
def test_find_cfg(happi_cfg: str):
    # Use our config directory
    assert happi_cfg == Client.find_config()
    # Set the path explicitly using HAPPI env variable
    os.environ['HAPPI_CFG'] = happi_cfg
    assert happi_cfg == Client.find_config()