.. _containers: Containers ********** In order to regulate and template the information that gets entered into the Happi database, we use the concept of containers. Containers serve two primary roles: * Identifying how to instantiate the object it represents (by way of class name, arguments, and keyword arguments). * Storing pertinent and structured metadata for the given instance. Containers are created by instantiating the :class:`.HappiItem` class or a subclass of it. The metadata associated with the instance is broken up into fields or "entries" of type :class:`.EntryInfo`. This allows a developer to specify fields that are essential to every instance of a specific container type. EntryInfo ^^^^^^^^^ These fields are specified using an instance of :class:`.EntryInfo`. This class provides several primary features: * Mark a field as required (or optional) * Add default values when unspecified * Enforce - or validate - a certain format for the field .. autoclass:: happi.EntryInfo :members: HappiItem ^^^^^^^^^ In order to ensure that information is entered into the database in an organized fashion, the client will only accept classes that inherit from :class:`.HappiItem`. Each item will have the key information represented as class attributes, available to be manipulated like any other regular property Editing the information for a container is a simple as: .. ipython:: python from happi import HappiItem item = HappiItem(name='my_device') item.name = 'new_name' .. note:: :class:`happi.Device` class is **deprecated** due to ambiguous name, conflicting with :class:`ophyd.Device`. :class:`happi.HappiItem` should be used instead. Example Container ^^^^^^^^^^^^^^^^^ In order to show the flexibility of the :class:`.EntryInfo` class, we'll put together a new example container. The class can be invoked in the same way you would usually handle class inheritance, the only difference is that you specify class attributes as EntryInfo objects: .. ipython:: python import re from happi import HappiItem, EntryInfo class MyItem(HappiItem): """My new item, with a known model number.""" model_no = EntryInfo('Model Number of Item', optional=False) count = EntryInfo('Count of Item', enforce=int, default=0) choices = EntryInfo('Choice Info', enforce=['a','b','c']) no_whitespace = EntryInfo('Enforce no whitespace', enforce = re.compile(r'[\S]*$'), enforce_doc = 'This item cannot have whitespace') By default, :class:`.EntryInfo` will create an optional init keyword argument with a default of ``None`` with the same name as the class attribute. A quick way to see how this information will be put into the the database is taking a look at ``dict(item)``: .. ipython:: python item = MyItem(name="my_item", model_no="QABC1234") dict(item) As shown in the example above, using the EntryInfo keywords, you can put a short docstring to give a better explanation of the field, and also enforce that user enter a specific format of information. While the user will always be able to enter ``None`` for the attribute, if a real value is given it will be checked against the specified ``enforce`` keyword, raising ``ValueError`` if invalid. Here is a table for how the :class:`.EntryInfo` check the type ======== =========================== Enforce Method of enforcement ======== =========================== None Any value will work type type(value) list list.index(value) regex regex.match(value) != None function function(value) ======== =========================== If your enforce condition is complicated or obfuscated, you can add a docstring using the ``enforce_doc`` keyword that explains the rule. (This may be helpful for regex matches which are difficult for humans to read) Fields that are important to the item can be marked as mandatory with ``optional=False`` and should have no default value. When entering information you will not necessarily see a difference between optional and mandatory :class:`.EntryInfo`, however the database client will reject the item if these fields do not have the requisite values set. Loading your Object ^^^^^^^^^^^^^^^^^^^ A container's primary role is containing the information necessary to load the Python representation of an object. Internally, happi keeps track of all containers by way of its registry, the :class:`~happi.containers.HappiRegistry`. This information is stored as a ``device_class``, ``args`` and ``kwargs``. The former stores a string that indicates the Python class of the item, the other two indicate the information that is needed to instantiate it. With this information both :func:`.from_container` and :func:`.load_device` are able to handle importing modules and instantiating your object. .. note:: happi will attach the original metadata with a fixed attribute name ``.md`` to your object. You can use this to keep track of the container metadata used to instantiate your object. This can be disabled by setting ``attach_md`` to ``False`` in :func:`.from_container`. Often, information contained in the ``args`` or ``kwargs`` will be duplicated in other parts of the container. For instance, most ``ophyd`` objects will want a ``name`` and ``prefix`` on initialization. Instead of repeating that information you can just use a template and have the information automatically populated for you by the container itself. For instance, in the aforementioned example, ``container.args = ["{{name}}"]`` would substitute the value of ``container.name`` in as an argument. If the template contains the substituted attribute alone, the type will also be converted. .. ipython:: python from happi import from_container container = MyItem(name="my_item", model_no="QABC1234", device_class='ophyd.sim.SynSignal', kwargs={'name': '{{name}}'}) obj = from_container(container, attach_md=True) obj Integrating with your package ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Happi provides some containers for your convenience, but intentionally does not support all use cases or control systems in a monolithic fashion. The suggested method for supporting your new package would be to make your package have a dependency on happi, of course, and subclass :class:`.HappiItem` to make a new container in your own package. Then, add an `entry point `_ specified by the **happi.containers** keyword to your package's ``setup.py``. Example entry points can be found `here `_. :class:`~happi.containers.HappiRegistry` takes care of loading the entry points and making them available throughout the library. .. _convention_label: Built-in Container Conventions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In order for the database to be as easy to parse as possible we need to establish conventions on how information is entered. Please read through this before entering any information into the database. HappiItem Entries +++++++++++++++++ These are fields that are common to all Happi items. **name** This is simply a short name we can use to refer to the item. **device_class** This is the full class name, which lets happi know how to instantiate your item. The ``device_class`` name remains for backward-compatibility reasons. Thinking of it as ``class_name`` or ``creator_callable`` would be more apt. .. note:: This may also be the name of a factory function - happi only cares that it's callable. **args** Argument list to be passed on instantiation. May contain templated macros such as ``{{name}}``. **kwargs** Keyword argument dictionary to be passed on instantiation. May contain templated macros such as ``{{name}}``. **active** Is the object actively deployed? **documentation** A brief set of information about the object. OphydItem entries +++++++++++++++++ `ophyd `_ has first-class support in happi - but not much is required on top of the base HappiItem to support it. **prefix** This should be the prefix for all of the PVs contained within the device. It does not matter if this is an invalid record by itself. LCLSItem entries ++++++++++++++++ This class is now part of `pcdsdevices `_. It remains documented here as PCDS is the original developer and primary user of happi as of the time of writing. If you intend to use the same metadata that we do, please copy and repurpose the ``LCLSItem`` class. **beamline** Beamline is required. While it is expected to be one of the following, it is not enforced:: CXI HXD ICL KFE LFE MEC MFX PBT RIX TMO TXI XCS XPP **z** Position of the device on the z-axis in the LCLS coordinates. **location_group** The group of this device in terms of its location. This is primarily used for LUCID's grid. **functional_group** The group of this device in terms of its function. This is primarily used for LUCID's grid. **stand** Acronym for stand, must be three alphanumeric characters like an LCLSI stand (e.g. DG3) or follow the LCLSII stand naming convention (e.g. L0S04). **lightpath** If the device should be included in the LCLS `Lightpath `_. **embedded_screen** Path to an embeddable PyDM control screen for this device. **detailed_screen** Path to a detailed PyDM control screen for this device. **engineering_screen** Path to a detailed engineering PyDM control screen for this device.