plesty.lib.device.base_device_sync ================================== .. py:module:: plesty.lib.device.base_device_sync .. autoapi-nested-parse:: Base synchronous device model for Plesty. Classes ------- .. autoapisummary:: plesty.lib.device.base_device_sync.BaseDeviceSyncModel Module Contents --------------- .. py:class:: BaseDeviceSyncModel(id: str, op_schema=None, param_schema=None, op_solver=None, cmd_solver=None, data_path: str | os.PathLike[str] | None = None, allowed_data_roots: collections.abc.Iterable[str | os.PathLike[str]] | None = None, **kwargs) Bases: :py:obj:`abc.ABC`, :py:obj:`plesty.lib.device.params.ConfigSystem`, :py:obj:`plesty.lib.device.funcs.FunctionSystem`, :py:obj:`plesty.lib.device.telemetry.TelemetrySystem` Abstract base class for synchronous device models in Plesty. Initialize the device model with optional schemas and solvers. :param id: Device identifier, combined with the host IP into the resource key. :param op_schema: Optional operation schema for the function system. :param param_schema: Optional parameter schema for the configuration system. :param op_solver: Optional operation solver bound to the function system. :param cmd_solver: Optional command solver bound to the configuration system. :param data_path: Default directory for acquired data, used until a client calls :meth:`set_data_path`. Device modules read it from their own configuration source (YAML, ``.env``) and pass it in — the library does not prescribe a format. :param allowed_data_roots: Directories that client-supplied paths must resolve under. Defaults to ``[data_path]``, so a device can only be redirected inside its own data directory unless wider roots are declared explicitly. :param \*\*kwargs: Extra keyword arguments, accepted and ignored so subclasses can pass their own construction options up the chain. :raises ValueError: If ``data_path`` does not resolve under ``allowed_data_roots``. .. py:attribute:: id .. py:attribute:: _resources :type: list[Any] :value: [] .. py:attribute:: _resource_manager :type: plesty.lib.service.resource_manager.ResourceManager | None :value: None .. py:attribute:: _allowed_data_roots :type: list[pathlib.Path] .. py:attribute:: _root_spellings :type: list[tuple[pathlib.Path, pathlib.Path]] .. py:attribute:: _default_data_path :type: pathlib.Path | None :value: None .. py:attribute:: _data_path :type: pathlib.Path | None :value: None .. py:attribute:: _data_path_display :type: pathlib.Path | None .. py:method:: register_resources(resources: list[Any]) -> None Declare the physical resources this device owns. Extends the device's resource inventory and registers the same set with the embedded :class:`~plesty.lib.service.resource_manager.ResourceManager` so that direct (non-TCP) clients can request exclusive access via :meth:`allocate_resource`. :param resources: Resource identifiers in any format accepted by :class:`~plesty.lib.service.resource_manager.ResourceManager` (flat list, set, or nested dict tree). .. py:method:: allocate_resource(resources: Any, client_id: str) -> bool Request exclusive access to one or more device resources. Delegates to the embedded :class:`~plesty.lib.service.resource_manager.ResourceManager`. Returns ``True`` if the allocation succeeded, ``False`` if any requested resource is already held by another client. When no resources have been registered, all requests are granted. :param resources: Resource identifiers (str, list, or dict tree). :param client_id: Stable identifier for the requesting client. .. py:method:: release_resource(client_id: str) -> None Release all resources previously allocated to *client_id*. :param client_id: Identifier of the client whose allocation should be freed. .. py:method:: client_resources(client_id: str) -> list[str] Return the canonical resource paths currently allocated to *client_id*. :param client_id: Identifier of the client to query. .. py:method:: _is_allowed_data_path(path: pathlib.Path) -> bool Return whether *path* resolves under one of the allowed data roots. .. py:method:: _display_path(resolved: pathlib.Path) -> pathlib.Path Re-base *resolved* onto the configured spelling of its data root. ``Path.resolve()`` rewrites a mapped Windows drive to its UNC form, which the operator never configured and some vendor software rejects. A path under a known root is expressed under that root's configured spelling; anything else is returned as it came. .. py:method:: _resolve_data_path(path: str | os.PathLike[str]) -> pathlib.Path Resolve *path* to an absolute directory, expanding ``~`` and following links. A relative path is taken relative to the configured default data path (or, when only roots are configured, the first allowed root), so a client can route a run with ``set_data_path("session-42")`` without knowing the server's filesystem layout. :param path: Absolute or relative directory supplied by the caller. :raises ValueError: If *path* is relative and no data root is configured. .. py:method:: set_data_path(path: str | os.PathLike[str], **kwargs) -> str Set the directory where this device stores acquired data. The path is resolved (``~`` expanded, ``..`` collapsed, symlinks followed) and must lie under one of the device's allowed data roots; it is created if missing. All clients of a device server share one data path — the last call wins, matching the single-operator resource-lock model. :param path: Target directory, absolute or relative to the device's default data path. :param \*\*kwargs: Optional extras such as ``resources``, ignored here. :returns: The directory now in effect, in the configured spelling of its data root — a mapped drive stays a mapped drive; resolution is used for the containment checks only. :raises ValueError: If the resolved path is outside the allowed roots, or the device has no data path configured at all. .. py:method:: get_data_path(**kwargs) -> str Return the directory currently used for acquired data. Device code that writes acquisition files should read the directory from here rather than from its own configuration, so client-set paths take effect. :param \*\*kwargs: Optional extras such as ``resources``, ignored here. :returns: the last one set by a client, or the device's configured default. The directory is created if missing — the configured default is otherwise never created anywhere (construction is side-effect-free), and vendor software validating the directory rejects one that does not exist (plesty-lib#31). :rtype: The directory in effect, in its configured spelling :raises RuntimeError: If the device has no data path configured. .. py:method:: init(main=None) -> None :abstractmethod: Initialize the device. This should be called before any other operations. .. py:property:: is_operatable :type: bool Check if the device is currently operatable (connected and responsive). .. py:property:: state :type: dict Return a dictionary representing the current device state with all parameters. .. py:method:: get_state() -> dict[str, Any] Return a dictionary of all configuration parameters and their current values. .. py:method:: synchronize_param_from_device(keys: list[str] | None = None, sync_constraints: bool = False) -> None Synchronizing the model's configuration with the actual device. .. py:method:: doc_model() -> plesty.lib.device.doc.DeviceDocModel Return this device's structured, render-agnostic documentation model. The model bundles the configuration parameters (grouped) and every callable operation (schema-registered plus ``@expose_to_api`` custom methods, unified). Documentation renderers consume it instead of re-deriving the metadata. :returns: The :class:`~plesty.lib.device.doc.DeviceDocModel` for this device. .. py:method:: summary(style: str = 'short', filename=None) -> str Summarize both configuration parameters and registered functions. :param style: One of ``short``, ``md``, or ``google``. :param filename: Optional file path to save the rendered summary. :returns: A rendered summary string combining parameter and function sections. .. py:method:: get_query_command(key: str) -> str | None Return the command that *would* be sent to query *key*, sending nothing. An accessor, not an action — which is the whole difference from :meth:`query`. ``query(key)`` performs the exchange, consumes the reply and returns the *value*; this returns the *command*, touches no I/O and leaves the session exactly as it found it. The naming follows the split already in this class: ``get_*`` reads what the device declares, a bare verb talks to it. It exists so a caller can transmit a query without reading its answer — the orphaned reply the buffer-drain gate needs. That takes the command and the transport's :meth:`~plesty.lib.traffic.TrafficManager.orphan`, and the command is the half only the device knows. :param key: Configuration key. :returns: The command, or ``None`` when this device cannot produce one — the base implementation, since a solver is bound by the subclass. The drain gate then skips and says so. .. py:method:: query_param_range(key: str) -> tuple[float | int | None, float | int | None] Query the device for the valid range of a numeric parameter. .. py:method:: query_param_options(key: str) -> list[Any] | None Query the device for the valid options of a categorical parameter. .. py:method:: connect() -> Any :abstractmethod: Placeholder method for connecting to the device. .. py:method:: disconnect() -> Any :abstractmethod: Placeholder method for disconnecting from the device. .. py:method:: identity() -> str :abstractmethod: Query the device for its identity string. .. py:method:: check_errors() -> list[str] :abstractmethod: Query the device for any error messages. .. py:method:: check_operatability() -> bool :abstractmethod: Check if the device is currently operatable (connected and responsive). .. py:method:: __enter__() Enter the device context: acquire the resource lock, init and connect. .. py:method:: __exit__(exc_type, exc_value, tb) Exit the device context, disconnecting and releasing resources. The resource lock is released even when ``disconnect()`` fails. A transport that throws on the way out — a clear refused by a wedged session, a close on an already-dead handle — must not leave the device marked as in use: every later attempt then fails with "Resource already in use" against an instrument nothing is holding, and the process has to be restarted to clear it. .. py:method:: response_to_target_type(key, response) -> Any Parse a raw device response into the target type for the given parameter key. .. py:method:: _user_permission_check(user_resources: list[Any], key: str) -> bool Check if the client is permitted to access *key* given its allocated resources. The default implementation is permissive (returns ``True``). Subclasses should override this method with device-specific logic, and may call :meth:`client_resources` or inspect :attr:`_resource_manager` to query current allocations. :param user_resources: Resources reported by the client for this request. :param key: The configuration parameter or function key being accessed. .. py:method:: write(key: str, value: str | float | int | bool, **kwargs) -> bool Write a configuration parameter value to the device after validation. :param key: Name of the parameter to write (as listed on the Parameters page). :param value: New value; validated against the parameter's type and range before being sent to the device. :param \*\*kwargs: Optional extras such as ``resources`` for per-client access control. :returns: ``True`` if the write succeeded, ``False`` otherwise (e.g. a permission check failed). .. py:method:: query(key: str, **kwargs) -> Any Query a configuration parameter value from the device. :param key: Name of the parameter to read (as listed on the Parameters page). :param \*\*kwargs: Optional extras such as ``resources`` for per-client access control. :returns: The parameter's current value, converted to its declared type. .. py:method:: _write_(key: str, value: str | float | int | bool) -> bool :abstractmethod: Placeholder method for writing a command to the device. .. py:method:: _query_(key: str) -> str :abstractmethod: Placeholder method for querying the device. .. py:method:: reset() -> None Reset the device to a known state. .. py:method:: clear() -> None Clear the device state.