From 79740e7992f73d605d198f3582a8dfd62ac40608 Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sat, 11 Oct 2025 10:21:33 -0400 Subject: [PATCH 01/10] DOC: edit help for 'save-and-restore' tool --- src/save_and_restore_api/tools/cli.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/save_and_restore_api/tools/cli.py b/src/save_and_restore_api/tools/cli.py index 864e6f2..c692147 100644 --- a/src/save_and_restore_api/tools/cli.py +++ b/src/save_and_restore_api/tools/cli.py @@ -87,7 +87,6 @@ def formatter(prog): parser = argparse.ArgumentParser( description="save-and-restore: CLI tool for operations on Save-and-Restore nodes.\n" f"save-and-restore-api version {version}\n\n" - "The tool connects to the save-and-restore service to perform the selected operation. \n" "The following operations are currently supported:\n" " LOGIN: check user login credentials.\n" " CONFIG GET: read an existing config node.\n" @@ -106,8 +105,11 @@ def formatter(prog): " SAVE_AND_RESTORE_API_USER_PASSWORD: user password (use with caution).\n" "CLI parameters override the respective environment variables.\n" "\n" - "The program returns 0 if the operation is successful. Non-zero return codes\n" - "indicate errors.\n" + "\n" + "Exit codes:\n" + " 0: operation successful;\n" + " 1: error in command line parameters;\n" + " 2: operation failed.\n" "\n" "Examples:\n" "\n" From 8ccc3cdd19bd830a2f497e99ba2eaf678b364f1f Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 12:58:56 -0400 Subject: [PATCH 02/10] DOC: write usage.rst --- docs/source/usage.rst | 167 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index e6300a9..1e896f9 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -1,3 +1,170 @@ ===== Usage ===== + +The **save-and-restore-api** package provides a Python API library and **save-and-restore** +CLI tool. The library is Python developer-friendly interface for communication with +Save-and-Restore service via REST API. The **save-and-restore** CLI tool allows to perform +some operations on Save-and-Restore nodes from the command line and can be used in Bash +scripts. + +'save-and-restore-api' Python library +===================================== + +**save-and-restore-api** is a Python library for communicating with Save-and-Restore service +(CS Studio Phoebus). The package provides syncronous (thread-based) and asynchronous (asyncio) +versions of the API classes. The synchronous ``SaveRestoreAPI`` class is located in the +``save_and_restore_api`` module and the asynchronous counterpart is located in the +``save_and_restore_api.aio`` module. Both classes provide the same set of methods and properties. +The library is built on top of the **httpx** package. + +All API requests to Save-and-Restore service except GET requests require authentication. +There are two ways to set authentication credentials. The first way is to call the ``auth_set()`` +method of the API class instance. The method sets credentials for all subsequent API calls. +Repeatedly calling the method overwrites previously set credentials. The second way is to +generate the authentication object by calling the ``auth_gen()`` method and pass the returned +object to the ``auth`` parameter of the API methods. This method may be useful if the application +simultaneously supports multiple users with different credentials. + +The ``SaveRestoreAPI`` class supports context manager protocol. The class can be used with +and without context manager. The choice depends on the application architecture. The context +manager automatically opens and closes the underlying HTTP socket: + +.. code-block:: python + + from save_and_restore_api import SaveRestoreAPI + + with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: + + # < Set authentication if necessary > + SR.auth_set(username="user", password="user_password") + + # < The code that communicates with Save-and-Restore service using SR object> + +If using the class without context manager, the application is responsible for opening +and closing the HTTP socket: + +.. code-block:: python + + try: + SR = SaveRestoreAPI(base_url="http://localhost:8080/save-restore") + SR.open() + + # < Set authentication if necessary > + SR.auth_set(username="user", password="user_password") + + # < The code that communicates with Save-and-Restore service using SR object> + + finally: + SR.close() + + +Examples +======== + +The following example code creates a folder node named "My Folder" under the root node: + +.. code-block:: python + + from save_and_restore_api import SaveRestoreAPI + + with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: + SR.auth_set(username="user", password="user_password") + + root_folder_uid = SR.ROOT_NODE_UID + node={"name": "My Folder", "nodeType": "FOLDER"} + + folder = SR.node_add(root_folder_uid, node=node) + + print(f"Created folder metadata: {folder}") + +Here is an async version of the same code: + +.. code-block:: python + + from save_and_restore_api.aio import SaveRestoreAPI + + async with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: + await SR.auth_set(username="user", password="user_password") + + root_folder_uid = SR.ROOT_NODE_UID + node={"name": "My Folder", "nodeType": "FOLDER"} + + folder = await SR.node_add(root_folder_uid, node=node) + print(f"Created folder metadata: {folder}") + +'save-and-restore' CLI tool +=========================== + +**save-and-restore** CLI tool is installed with the package. The tool allows performing +a limited set of basic operations on the nodes of the Save-and-Restore service. +The currently selected set of operations: + +- **LOGIN**: test login credentials; + +- **CONFIG ADD**: create configuration node based on a list of PVs read from a file; + +- **CONFIG UPDATE**: update an existing configuration node based on a list of PVs read from a file; + +- **CONFIG GET**: get information about an existing configuration node, including the list of PVs. + +The tool was primarily developed for adding snapshot configurations to Save-and-Restore +based on lists of PVs loaded from local files. Typical use case is to create a configuration +based on a list of PVs read from an autosave (``.sav``) file saved by an IOC. Currently only +autosave files are supported, but support for other formats can be added if needed. +The list of supported functions can also be extended. + +There are multiple ways to pass authentication credentials to the tool. The credentials include +user name and password. The user name can be passed using the ``--user-name`` command line +parameter. The tool interactively prompts for the password of the operation requires +authentication. If the user name is not specified, then the tool also prompts for it. +Alternatively, the user name and/or password can be passed using environment variables. + +The following environment variables are supported: + +- ``SAVE_AND_RESTORE_API_BASE_URL``: host URL (see '--base-url' parameter) +- ``SAVE_AND_RESTORE_API_USER_NAME``: user name (see '--user-name' parameter); +- ``SAVE_AND_RESTORE_API_USER_PASSWORD``: user password. + +Examples of using 'save-and-restore' CLI tool +============================================= + +Check login credentials. User password is requested interactively. Alternatively, the +password can be passed using environment variable ``SAVE_AND_RESTORE_API_USER_PASSWORD``. + +.. code-block:: bash + + save-and-restore --base-url http://localhost:8080/save-restore --user-name=user LOGIN + +Read the configuration node named 'eiger_config'. Print the full configuration data +(the list of PVs): + +.. code-block:: bash + + save-and-restore --base-url http://localhost:8080/save-restore \ + CONFIG GET --config-name /detectors/imaging/eiger_config --show-data=ON + +Create a new configuration node named 'eiger_config'. Load the list of PVs from +file ``eiger_pvs.sav``. Automatically create any missing parent folders in +the path: + +.. code-block:: bash + + save-and-restore --base-url=http://localhost:8080/save-restore --user-name=user \ + --create-folders=ON CONFIG ADD --config-name=/detectors/imaging/eiger_config \ + --file-name=eiger_pvs.sav --file-format=autosave + +Update an existing configuration node named 'eiger_config'. Load the list of PVs +from the file ``eiger_pvs.sav``: + +.. code-block:: bash + + save-and-restore --base-url http://localhost:8080/save-restore --user-name=user \ + CONFIG UPDATE --config-name /detectors/imaging/eiger_config \ + --file-name eiger_pvs.sav --file-format autosave + +Print full list of options: + +.. code-block:: bash + + save-and-restore -h From 5f691719185973e9167b0ea202ed7768900fb663 Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 13:01:29 -0400 Subject: [PATCH 03/10] ENH: delete commented code --- src/save_and_restore_api/_api_base.py | 32 --------------------------- 1 file changed, 32 deletions(-) diff --git a/src/save_and_restore_api/_api_base.py b/src/save_and_restore_api/_api_base.py index 0dbfe21..41e51b3 100644 --- a/src/save_and_restore_api/_api_base.py +++ b/src/save_and_restore_api/_api_base.py @@ -442,35 +442,3 @@ def _prepare_structure_path_nodes(self, *, path): method, url = "GET", "/path" params = {"path": path} return method, url, params - - # ============================================================================================= - - # def create_config(self, parent_node_uid, name, pv_list): - # config_dict = { - # "configurationNode": { - # "name": name, - # "nodeType": "CONFIGURATION", - # "userName": self._username, - # }, - # "configurationData": { - # "pvList": pv_list, - # }, - # } - # print(f"config_dict=\n{pprint.pformat(config_dict)}") - # return self.send_request("PUT", f"/config?parentNodeId={parent_node_uid}", body_json=config_dict) - - # def update_config(self, node_uid, name, pv_list): - # config_dict = { - # "configurationNode": { - # "name": name, - # "nodeType": "CONFIGURATION", - # "userName": self._username, - # "uniqueId": node_uid, - # }, - # "configurationData": { - # "pvList": pv_list, - # }, - # } - # print(f"config_dict=\n{pprint.pformat(config_dict)}") - # # return self.send_request("POST", f"/config/{node_uid}", body_json=config_dict) - # return self.send_request("POST", "/config", body_json=config_dict) From 3b7c95cd8e70fd4c64db7147a72660c55a99e00e Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 17:30:34 -0400 Subject: [PATCH 04/10] DOC: docstrings for the 'node' and 'config' API --- src/save_and_restore_api/_api_threads.py | 170 +++++++++++++++++++++-- 1 file changed, 155 insertions(+), 15 deletions(-) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index d9ba375..464de03 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -214,18 +214,61 @@ def login(self, *, username=None, password=None): def node_get(self, uniqueNodeId): """ - Returns the node with specified node UID. + Returns the metadata for the node with specified node UID. API: GET /node/{uniqueNodeId} + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the node. + + Returns + ------- + dict + Node metadata as returned by the server. + + Examples + -------- + + .. code-block:: python + + from save_and_restore_api import SaveRestoreAPI + + with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: + root_folder_uid = SR.ROOT_NODE_UID + root_folder = SR.node_get(root_folder_uid) + print(f"Root folder metadata: {root_folder}") + + Async version: + .. code-block:: python + + from save_and_restore_api.aio import SaveRestoreAPI + + async with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: + root_folder_uid = SR.ROOT_NODE_UID + root_folder = await SR.node_get(root_folder_uid) + print(f"Root folder metadata: {root_folder}") """ method, url = self._prepare_node_get(uniqueNodeId=uniqueNodeId) return self.send_request(method, url) def nodes_get(self, uniqueIds): """ - Returns nodes specified by a list of UIDs. + Returns metadata for multiple nodes specified by a list of UIDs. This API is + similar to calling ``node_get()`` multiple times, but is more efficient. API: GET /nodes + + Parameters + ---------- + uniqueIds : list of str + List of node unique IDs. + + Returns + ------- + list of dict + List of node metadata as returned by the server. """ method, url, body_json = self._prepare_nodes_get(uniqueIds=uniqueIds) return self.send_request(method, url, body_json=body_json) @@ -262,7 +305,8 @@ def node_add(self, parentNodeId, *, node, auth=None, **kwargs): with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: SR.auth_set(username="user", password="user_password") root_folder_uid = SR.ROOT_NODE_UID - folder = SR.node_add(root_folder_uid, node={"name": "My Folder", "nodeType": "FOLDER"}) + node = {"name": "My Folder", "nodeType": "FOLDER"} + folder = SR.node_add(root_folder_uid, node=node) print(f"Created folder metadata: {folder}") Async version: @@ -274,7 +318,8 @@ def node_add(self, parentNodeId, *, node, auth=None, **kwargs): async with SaveRestoreAPI(base_url="http://localhost:8080/save-restore") as SR: await SR.auth_set(username="user", password="user_password") root_folder_uid = SR.ROOT_NODE_UID - folder = await SR.node_add(root_folder_uid, node={"name": "My Folder", "nodeType": "FOLDER"}) + node = {"name": "My Folder", "nodeType": "FOLDER"} + folder = await SR.node_add(root_folder_uid, node=node) print(f"Created folder metadata: {folder}") """ method, url, params, body_json = self._prepare_node_add(parentNodeId=parentNodeId, node=node) @@ -283,28 +328,64 @@ def node_add(self, parentNodeId, *, node, auth=None, **kwargs): def node_delete(self, nodeId, *, auth=None): """ Deletes the node with specified node ID. The call fails if the node can - not be deleted. + not be deleted, e.g. the node is a non-empty folder. API: DELETE /node/{nodeId} + + Parameters + ---------- + nodeId : str + Unique ID of the node to be deleted. + auth : httpx.BasicAuth + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + None """ method, url = self._prepare_node_delete(nodeId=nodeId) return self.send_request(method, url, auth=auth) def nodes_delete(self, uniqueIds, *, auth=None): """ - Deletes multiple nodes specified as a list of UIDs. The call fails if - any of the nodes can not be deleted. + Deletes multiple nodes specified by the list of UIDs. The API goes through the nodes in the list + and deletes the nodes. It fails if the node can not be deleted and does not try to delete the + following nodes. API: DELETE /node + + Parameters + ---------- + uniqueIds : list[str] + List of UIDs of the nodes to delete. + auth : httpx.BasicAuth + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + None """ method, url, body_json = self._prepare_nodes_delete(uniqueIds=uniqueIds) return self.send_request(method, url, body_json=body_json, auth=auth) def node_get_children(self, uniqueNodeId): """ - Returns the list of child nodes for the specified node UID. + Returns the list of child nodes for the node with specified UID. API: GET /node/{uniqueNodeId}/children + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the node. + + Returns + ------- + list[dict] + List of child node nodes. The list elements are dictionaries containing + the node metadata as returned by the server. """ method, url = self._prepare_node_get_children(uniqueNodeId=uniqueNodeId) return self.send_request(method, url) @@ -314,6 +395,16 @@ def node_get_parent(self, uniqueNodeId): Returns the parent node for the specified node UID. API: GET /node/{uniqueNodeId}/parent + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the node. + + Returns + ------- + dict + Parent node metadata as returned by the server. """ method, url = self._prepare_node_get_parent(uniqueNodeId=uniqueNodeId) return self.send_request(method, url) @@ -324,10 +415,20 @@ def node_get_parent(self, uniqueNodeId): def config_get(self, uniqueNodeId): """ - Returns the config data for the node with specified node UID. Returns only the configuration - data. To get the node metadata use ``node_get()``. + Returns the configuration data for the node with specified node UID. Returns only + the configuration data. To get the node metadata use ``node_get()``. API: GET /config/{uniqueNodeId} + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the configuration node. + + Returns + ------- + dict + Configuration data (``configurationData``) as returned by the server. """ method, url = self._prepare_config_get(uniqueNodeId=uniqueNodeId) return self.send_request(method, url) @@ -339,8 +440,10 @@ def config_add(self, parentNodeId, *, configurationNode, configurationData, auth Minimum required fields: - configurationNode = {"name": "test_config"} - configurationData = {"pvList": [{"pvName": "PV1"}, {"pvName": "PV2"}]} + .. code-block:: python + + configurationNode = {"name": "test_config"} + configurationData = {"pvList": [{"pvName": "PV1"}, {"pvName": "PV2"}]} The fields ``uniqueId``, ``nodeType``, ``userName`` in ``configurationNode`` are ignored and overwritten by the server. @@ -349,6 +452,22 @@ def config_add(self, parentNodeId, *, configurationNode, configurationData, auth as returned by the server. API: PUT /config?parentNodeId={parentNodeId} + + Parameters + ---------- + parentNodeId : str + Unique ID of the parent node. + configurationNode : dict + Configuration node (``configurationNode``) metadata. The required field is ``name``. + configurationData : dict + Configuration data (``configurationData``). The required field is ``pvList``. + + Returns + ------- + dict + Dictionary contains configuration node metadata and configuration data of the node + that was added. The dictionary contains two keys: ``configurationNode`` and + ``configurationData`` as returned by the server. """ method, url, body_json = self._prepare_config_add( parentNodeId=parentNodeId, configurationNode=configurationNode, configurationData=configurationData @@ -357,11 +476,32 @@ def config_add(self, parentNodeId, *, configurationNode, configurationData, auth def config_update(self, *, configurationNode, configurationData, auth=None): """ - Updates an existing configuration node. Parameters ``configurationNode`` and ``configurationData`` - should be loaded using ``node_get()`` and ``config_get()`` respectively. Both parameters must - contain correct ``uniqueID`` field values. + Update an existing configuration node. It is best if ``configurationNode`` and ``configurationData`` + are loaded using ``node_get()`` and ``config_get()`` APIs respectively and then modified in the + application code. Both dictionaries can also be created from scratch in the application code if + necessary. Both dictionaries must contain correct correct UID (``uniqueID``) of an existing + configuration node. API: POST /config + + Parameters + ---------- + configurationNode : dict + Configuration node (``configurationNode``) metadata. ``uniqueId`` field must be point to + an existing configuration node. + configurationData : dict + Configuration data (``configurationData``). ``uniqueId`` field must be identical to the + ``uniqueId`` field in ``configurationNode``. + auth : httpx.BasicAuth + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + dict + Dictionary contains configuration node metadata and configuration data of the node + that was updated. The dictionary contains two keys: ``configurationNode`` and + ``configurationData`` as returned by the server. """ method, url, body_json = self._prepare_config_update( configurationNode=configurationNode, configurationData=configurationData From 56e37bd712350ef0c928e993f130716c03776466 Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 17:45:51 -0400 Subject: [PATCH 05/10] DOC: docstrings for 'tags' API --- src/save_and_restore_api/_api_threads.py | 42 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index 464de03..05dfe84 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -514,9 +514,16 @@ def config_update(self, *, configurationNode, configurationData, auth=None): def tags_get(self): """ - Returns all existing tags. + Returns the list of all existing tags. API: GET /tags + + Returns + ------- + list[dict] + List of all tags in the database. Each tag is dictionary with the following keys: ``name`` + - tag name, ``comment`` - tag comment (may be empty). The list may contain repeated elements. + Tags do not contain pointers to tagged nodes. """ method, url = self._prepare_tags_get() return self.send_request(method, url) @@ -527,16 +534,45 @@ def tags_add(self, *, uniqueNodeIds, tag, auth=None): dictionary must contain the ``name`` key and optionally ``comment`` key. API: POST /tags + + Parameters + ---------- + uniqueNodeIds : list of str + List of node unique IDs to which the tag should be added. + tag : dict + Tag to be added. The dictionary must contain the ``name`` key and optionally ``comment`` key. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + list[dict] + List of node metadata for the nodes to which the tag was added. """ method, url, body_json = self._prepare_tags_add(uniqueNodeIds=uniqueNodeIds, tag=tag) return self.send_request(method, url, body_json=body_json, auth=auth) def tags_delete(self, *, uniqueNodeIds, tag, auth=None): """ - Deletes ``tag`` to nodes specified by a list of UIDs ``uniqueNodeIds``. The ``tag`` - dictionary must contain the ``name`` key and optionally ``comment`` key. + Deletes ``tag`` from the nodes specified by a list of UIDs ``uniqueNodeIds``. The deleted + tag is identified by the ``"name"`` in the ``tag`` dictionary. The ``tag`` + dictionary may optionally ``comment`` key, but it is ignored by the API. API: DELETE /tags + + Parameters + ---------- + uniqueNodeIds : list[str] + List of node unique IDs from which the tag should be deleted. + tag : dict + Tag to be deleted. The dictionary must contain the ``name`` key. The ``comment`` key + is optional and ignored by the API. + + Returns + ------- + list[dict] + List of node metadata for the nodes from ``uniqueNodeIds`` list. """ method, url, body_json = self._prepare_tags_delete(uniqueNodeIds=uniqueNodeIds, tag=tag) return self.send_request(method, url, body_json=body_json, auth=auth) From b74fa5c1e9a7f146c7499624dd51c50645a6ba6d Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 18:52:25 -0400 Subject: [PATCH 06/10] DOC: docstings for the 'snapshots' API --- src/save_and_restore_api/_api_threads.py | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index 05dfe84..da7f5f5 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -588,6 +588,17 @@ def take_snapshot_get(self, uniqueNodeId): The returned list format matches the format of ``snapshotData["snapshotItems"]``. API: GET /take-snapshot/{uniqueNodeId} + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the configuration node. + + Returns + ------- + list[dict] + List of PV values read from the control system. Each PV is represented as a dictionary + of parameters. The format is consistent with the format of ``snapshotData["snapshotItems"]``. """ method, url = self._prepare_take_snapshot_get(uniqueNodeId=uniqueNodeId) return self.send_request(method, url) @@ -599,6 +610,27 @@ def take_snapshot_save(self, uniqueNodeId, *, name=None, comment=None, auth=None the name of the snapshot node and ``comment`` specifies the node description. API: PUT /take-snapshot/{uniqueNodeId} + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the configuration node. + name : str, optional + Name of the new snapshot node. If not specified or None, the name is set to + the date and time of the snapshot, e.g. ``2025-10-12 22:49:50.577``. + comment : str, optional + Description of the new snapshot node. If not specified or None, the comment + is set to date and time of the snapshot, e.g. ``2025-10-12 22:49:50.577``. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + dict + Dictionary contains snapshot node metadata and snapshot data of the node + that was created. The dictionary contains two keys: ``snapshotNode`` and + ``snapshotData`` as returned by the server. """ method, url, params = self._prepare_take_snapshot_save( uniqueNodeId=uniqueNodeId, name=name, comment=comment From 390c4654b7ee91381e63a4d7365fd3919fbe4608 Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 19:02:53 -0400 Subject: [PATCH 07/10] DOC: docstrings for snapshot API group --- src/save_and_restore_api/_api_threads.py | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index da7f5f5..3469a47 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -646,6 +646,16 @@ def snapshot_get(self, uniqueId): Returns snapshot data (``snapshotData``) for the snapshot specified by ``uniqueId``. API: GET /snapshot/{uniqueId} + + Parameters + ---------- + uniqueId : str + Unique ID of the snapshot node. + + Returns + ------- + dict + Snapshot data (``snapshotData``) as returned by the server. """ method, url = self._prepare_snapshot_get(uniqueId=uniqueId) return self.send_request(method, url) @@ -656,6 +666,25 @@ def snapshot_add(self, parentNodeId, *, snapshotNode, snapshotData, auth=None): under the existing configuration node specified by ``parentNodeId``. API: PUT /snapshot?parentNodeId={parentNodeId} + + Parameters + ---------- + parentNodeId : str + Unique ID of the parent configuration node. + snapshotNode : dict + Snapshot node (``snapshotNode``) metadata. The required field is ``"name"``. + snapshotData : dict + Snapshot data (``snapshotData``). The required field is ``"snapshotItems"``. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + dict + Dictionary contains snapshot node metadata and snapshot data of the node + that was added. The dictionary contains two keys: ``snapshotNode`` and + ``snapshotData`` as returned by the server. """ method, url, params, body_json = self._prepare_snapshot_add( parentNodeId=parentNodeId, snapshotNode=snapshotNode, snapshotData=snapshotData @@ -668,6 +697,25 @@ def snapshot_update(self, *, snapshotNode, snapshotData, auth=None): must have valid ``uniqueId`` fields pointing to an existing node. API: POST /snapshot + + Parameters + ---------- + snapshotNode : dict + Snapshot node (``snapshotNode``) metadata. ``uniqueId`` field must point to + an existing snapshot node. + snapshotData : dict + Snapshot data (``snapshotData``). ``uniqueId`` field must be identical to the + ``uniqueId`` field in ``snapshotNode``. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + dict + Dictionary contains snapshot node metadata and snapshot data of the node + that was updated. The dictionary contains two keys: ``snapshotNode`` and + ``snapshotData`` as returned by the server. """ method, url, body_json = self._prepare_snapshot_update( snapshotNode=snapshotNode, snapshotData=snapshotData @@ -679,6 +727,12 @@ def snapshots_get(self): Returns a list of all existing snapshots (list of ``snapshotNode`` objects). API: GET /snapshots + + Returns + ------- + list[dict] + List of snapshot nodes (``snapshotNode``) as returned by the server. The list + does not include snapshot data. """ method, url = self._prepare_snapshots_get() return self.send_request(method, url) From 19637c568d333b2b0df9403ea15acbe8a75292ce Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 19:23:00 -0400 Subject: [PATCH 08/10] DOC: docstrings for composite snapshot API group --- src/save_and_restore_api/_api_threads.py | 105 +++++++++++++++++++++-- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index 3469a47..bcd1c8f 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -743,11 +743,21 @@ def snapshots_get(self): def composite_snapshot_get(self, uniqueId): """ - Returns composite snapshot data (``compositeSnapshotData``). The composite snapshot is - specified by ``uniqueId``. The data includes uniqueId and the list of referencedSnapshotNodes - (no PV information). + Returns composite snapshot data (``compositeSnapshotData``) specified by ``uniqueId``. + The data includes uniqueId and the list of referencedSnapshotNodes (no PV information). + The composite snapshot node metadata can be obtained using ``node_get()``. API: GET /composite-snapshot/{uniqueId} + + Parameters + ---------- + uniqueId : str + Unique ID of the composite snapshot node. + + Returns + ------- + dict + Composite snapshot data (``compositeSnapshotData``) as returned by the server. """ method, url = self._prepare_composite_snapshot_get(uniqueId=uniqueId) return self.send_request(method, url) @@ -758,16 +768,38 @@ def composite_snapshot_get_nodes(self, uniqueId): specified by ``uniqueId``. API: GET /composite-snapshot/{uniqueId}/nodes + + Parameters + ---------- + uniqueId : str + Unique ID of the composite snapshot node. + + Returns + ------- + list[dict] + List of snapshot nodes. Each snapshot node is represented as a dictionary with + node metadata. No composite snapshot data is returned. """ method, url = self._prepare_composite_snapshot_get_nodes(uniqueId=uniqueId) return self.send_request(method, url) def composite_snapshot_get_items(self, uniqueId): """ - Returns a list of restorable items referenced by the composite snapshot. The composite snapshot is - specified by ``uniqueId``. + Returns a list of restorable items (PV data) referenced by the composite snapshot. + The composite snapshot is specified by ``uniqueId``. API: GET /composite-snapshot/{uniqueId}/items + + Parameters + ---------- + uniqueId : str + Unique ID of the composite snapshot node. + + Returns + ------- + list[dict] + List of snapshot items (PVs). The format is consistent with the format of + ``snapshotData["snapshotItems"]`` """ method, url = self._prepare_composite_snapshot_get_items(uniqueId=uniqueId) return self.send_request(method, url) @@ -778,6 +810,27 @@ def composite_snapshot_add(self, parentNodeId, *, compositeSnapshotNode, composi specified by ``parentNodeId``. API: PUT /composite-snapshot?parentNodeId={parentNodeId} + + Parameters + ---------- + parentNodeId : str + Unique ID of the parent configuration node. + compositeSnapshotNode : dict + Composite snapshot node (``compositeSnapshotNode``) metadata. The required field is ``"name"``. + compositeSnapshotData : dict + Composite snapshot data (``compositeSnapshotData``). The required field is + ``"referencedSnapshotNodes"``, which points to the list of UIDs of the nodes included in + the composite snapshot. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + dict + Dictionary contains composite snapshot node metadata and composite snapshot data + of the node that was added. The dictionary contains two keys: ``compositeSnapshotNode`` and + ``compositeSnapshotData`` as returned by the server. """ method, url, params, body_json = self._prepare_composite_snapshot_add( parentNodeId=parentNodeId, @@ -792,6 +845,25 @@ def composite_snapshot_update(self, *, compositeSnapshotNode, compositeSnapshotD must have valid ``uniqueId`` fields pointing to an existing node. API: POST /composite-snapshot + + Parameters + ---------- + compositeSnapshotNode : dict + Composite snapshot node (``compositeSnapshotNode``) metadata. ``uniqueId`` field must point to + an existing composite snapshot node. + compositeSnapshotData : dict + Composite snapshot data (``compositeSnapshotData``). ``uniqueId`` field must be identical to the + ``uniqueId`` field in ``compositeSnapshotNode``. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + dict + Dictionary contains composite snapshot node metadata and composite snapshot data + of the node that was updated. The dictionary contains two keys: ``compositeSnapshotNode`` and + ``compositeSnapshotData`` as returned by the server. """ method, url, body_json = self._prepare_composite_snapshot_update( compositeSnapshotNode=compositeSnapshotNode, @@ -801,9 +873,30 @@ def composite_snapshot_update(self, *, compositeSnapshotNode, compositeSnapshotD def composite_snapshot_consistency_check(self, uniqueNodeIds, *, auth=None): """ - Check consistency of the composite snapshots. + Check consistency of the composite snapshots. The snapshot is specified by the list of + UIDs of snapshots and composite snapshots included in the composite snapshot. + One of the use cases is to check if a snapshot can be added to an existing composite + snapshot. In this case the list of UIDs includes the UID of the exisitng composite + snapshot and the UID of the new snapshot to be added. The function returns a list + of PV data for each conflicting PV (composite snapshot items may not contain duplicate + PVs). API: POST /composite-snapshot-consistency-check + + Parameters + ---------- + uniqueNodeIds : list of str + List of UIDs of snapshots and composite snapshots included in the composite snapshot. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + then the authentication set using ``auth_set`` method is used. + + Returns + ------- + list[dict] + List of conflicting PVs. Each PV is represented as a dictionary of parameters. + If the list is empty, then there are no conflicts and the composite snapshot + can be created or updated. """ method, url, body_json = self._prepare_composite_snapshot_consistency_check(uniqueNodeIds=uniqueNodeIds) return self.send_request(method, url, body_json=body_json, auth=auth) From cd1016fea4fb9ac33f89ab4ea54cd0dc70c6dfb6 Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 19:38:09 -0400 Subject: [PATCH 09/10] DOC: docstrings for 'restore' and 'compare' API groups --- src/save_and_restore_api/_api_threads.py | 52 ++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index bcd1c8f..56943c2 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -799,7 +799,7 @@ def composite_snapshot_get_items(self, uniqueId): ------- list[dict] List of snapshot items (PVs). The format is consistent with the format of - ``snapshotData["snapshotItems"]`` + ``snapshotData["snapshotItems"]``. """ method, url = self._prepare_composite_snapshot_get_items(uniqueId=uniqueId) return self.send_request(method, url) @@ -896,7 +896,8 @@ def composite_snapshot_consistency_check(self, uniqueNodeIds, *, auth=None): list[dict] List of conflicting PVs. Each PV is represented as a dictionary of parameters. If the list is empty, then there are no conflicts and the composite snapshot - can be created or updated. + can be created or updated. The format is consistent with the format of + ``snapshotData["snapshotItems"]``. """ method, url, body_json = self._prepare_composite_snapshot_consistency_check(uniqueNodeIds=uniqueNodeIds) return self.send_request(method, url, body_json=body_json, auth=auth) @@ -908,10 +909,22 @@ def composite_snapshot_consistency_check(self, uniqueNodeIds, *, auth=None): def restore_node(self, nodeId, *, auth=None): """ Restore PVs based on the data from an existing snapshot node specified by nodeId. - Returns a list of snapshotItems that were NOT restored. Ideally the list should be empty. API: POST /restore/node + + Parameters + ---------- + nodeId : str + Unique ID of the snapshot node. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). + + Returns + ------- + list[dict] + List of snapshot items (PVs) that were NOT restored. The format is consistent with + the format of ``snapshotData["snapshotItems"]``. """ method, url, params = self._prepare_restore_node(nodeId=nodeId) return self.send_request(method, url, params=params, auth=auth) @@ -924,6 +937,20 @@ def restore_items(self, *, snapshotItems, auth=None): Returns a list of snapshotItems that were NOT restored. Ideally the list should be empty. API: POST /restore/items + + Parameters + ---------- + snapshotItems : list of dict + List of snapshot items (PVs) to be restored. The format is consistent with + the format of ``snapshotData["snapshotItems"]``. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen`` method). + + Returns + ------- + list[dict] + List of snapshot items (PVs) that were NOT restored. The format is consistent with + the format of ``snapshotData["snapshotItems"]``. """ method, url, body_json = self._prepare_restore_items(snapshotItems=snapshotItems) return self.send_request(method, url, body_json=body_json, auth=auth) @@ -939,6 +966,25 @@ def compare(self, nodeId, *, tolerance=None, compareMode=None, skipReadback=None the snapshot. API: GET /compare/{nodeId} + + Parameters + ---------- + nodeId : str + Unique ID of the snapshot or composite snapshot node. + tolerance : float, optional + Tolerance for numerical comparisons. If not specified or None, the default is 0. + compareMode : str, optional + Comparison mode. Supported values: ``"ABSOLUTE"``, ``"RELATIVE"``. + skipReadback : bool, optional + If True, then ``pvName`` live value is used for PVs with specified ``readbackPvName``. + If not specified, then the default is False and the readback PV is used for comparison. + + Returns + ------- + list[dict] + List of comparison results for each PV in the snapshot. Each PV is represented as a + dictionary with the following keys: ``pvName``, ``equal``, ``compare``, ``storedValue``, + ``liveValue``. """ method, url, params = self._prepare_compare( nodeId=nodeId, tolerance=tolerance, compareMode=compareMode, skipReadback=skipReadback From 6a7da09086b2c5911c912b739eecbe72f7723736 Mon Sep 17 00:00:00 2001 From: Dmitri Gavrilov Date: Sun, 12 Oct 2025 21:44:42 -0400 Subject: [PATCH 10/10] DOC: remaining docstrings --- src/save_and_restore_api/_api_threads.py | 125 ++++++++++++++++++----- tests/common.py | 5 +- 2 files changed, 100 insertions(+), 30 deletions(-) diff --git a/src/save_and_restore_api/_api_threads.py b/src/save_and_restore_api/_api_threads.py index 56943c2..22e8829 100644 --- a/src/save_and_restore_api/_api_threads.py +++ b/src/save_and_restore_api/_api_threads.py @@ -104,7 +104,7 @@ def send_request( Timeout for this request in seconds. If not specified or None, the default timeout set in the class constructor is used. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, + Object with authentication data (generated using ``auth_gen()`` method). If not specified or None, then the authentication set using ``auth_set`` method is used. Returns @@ -287,8 +287,7 @@ def node_add(self, parentNodeId, *, node, auth=None, **kwargs): Node metadata. The required fields are ``name`` and ``nodeType``. Supported node types: ``"FOLDER"``, ``"CONFIGURATION"``. auth : httpx.BasicAuth - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -337,8 +336,7 @@ def node_delete(self, nodeId, *, auth=None): nodeId : str Unique ID of the node to be deleted. auth : httpx.BasicAuth - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -360,8 +358,7 @@ def nodes_delete(self, uniqueIds, *, auth=None): uniqueIds : list[str] List of UIDs of the nodes to delete. auth : httpx.BasicAuth - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -493,8 +490,7 @@ def config_update(self, *, configurationNode, configurationData, auth=None): Configuration data (``configurationData``). ``uniqueId`` field must be identical to the ``uniqueId`` field in ``configurationNode``. auth : httpx.BasicAuth - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -542,8 +538,7 @@ def tags_add(self, *, uniqueNodeIds, tag, auth=None): tag : dict Tag to be added. The dictionary must contain the ``name`` key and optionally ``comment`` key. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -568,6 +563,8 @@ def tags_delete(self, *, uniqueNodeIds, tag, auth=None): tag : dict Tag to be deleted. The dictionary must contain the ``name`` key. The ``comment`` key is optional and ignored by the API. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -622,8 +619,7 @@ def take_snapshot_save(self, uniqueNodeId, *, name=None, comment=None, auth=None Description of the new snapshot node. If not specified or None, the comment is set to date and time of the snapshot, e.g. ``2025-10-12 22:49:50.577``. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -676,8 +672,7 @@ def snapshot_add(self, parentNodeId, *, snapshotNode, snapshotData, auth=None): snapshotData : dict Snapshot data (``snapshotData``). The required field is ``"snapshotItems"``. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -707,8 +702,7 @@ def snapshot_update(self, *, snapshotNode, snapshotData, auth=None): Snapshot data (``snapshotData``). ``uniqueId`` field must be identical to the ``uniqueId`` field in ``snapshotNode``. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -822,8 +816,7 @@ def composite_snapshot_add(self, parentNodeId, *, compositeSnapshotNode, composi ``"referencedSnapshotNodes"``, which points to the list of UIDs of the nodes included in the composite snapshot. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -855,8 +848,7 @@ def composite_snapshot_update(self, *, compositeSnapshotNode, compositeSnapshotD Composite snapshot data (``compositeSnapshotData``). ``uniqueId`` field must be identical to the ``uniqueId`` field in ``compositeSnapshotNode``. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -888,8 +880,7 @@ def composite_snapshot_consistency_check(self, uniqueNodeIds, *, auth=None): uniqueNodeIds : list of str List of UIDs of snapshots and composite snapshots included in the composite snapshot. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). If not specified or None, - then the authentication set using ``auth_set`` method is used. + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -918,7 +909,7 @@ def restore_node(self, nodeId, *, auth=None): nodeId : str Unique ID of the snapshot node. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -944,7 +935,7 @@ def restore_items(self, *, snapshotItems, auth=None): List of snapshot items (PVs) to be restored. The format is consistent with the format of ``snapshotData["snapshotItems"]``. auth : httpx.BasicAuth, optional - Object with authentication data (generated using ``auth_gen`` method). + Object with authentication data (generated using ``auth_gen()`` method). Returns ------- @@ -1000,6 +991,18 @@ def filter_add(self, filter, *, auth=None): Add a filter to the list stored in the database. API: PUT /filter + + Parameters + ---------- + filter : dict + Filter to be added. The dictionary must contain the ``name`` and ``filter`` keys. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen()`` method). + + Returns + ------- + dict + Added filter as returned by the server. """ method, url, body_json = self._prepare_filter_add(filter=filter) return self.send_request(method, url, body_json=body_json, auth=auth) @@ -1009,6 +1012,11 @@ def filters_get(self): Get the list of all the filters from the database. API: GET /filters + + Returns + ------- + list[dict] + List of all filters in the database. """ method, url = self._prepare_filters_get() return self.send_request(method, url) @@ -1018,6 +1026,17 @@ def filter_delete(self, name, *, auth=None): Delete filter with the given name from the database. API: DELETE /filter/{name} + + Parameters + ---------- + name : str + Name of the filter to be deleted. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen()`` method). + + Returns + ------- + None """ method, url = self._prepare_filter_delete(name=name) return self.send_request(method, url, auth=auth) @@ -1032,6 +1051,20 @@ def structure_move(self, nodeIds, *, newParentNodeId, auth=None): by ``newParentNodeId``. The API requires 'admin' priviledges. API: POST /move + + Parameters + ---------- + nodeIds : list[str] + List of node unique IDs to be moved. + newParentNodeId : str + Unique ID of the new parent node. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen()`` method). + + Returns + ------- + dict + Dictionary with metadata for the new parent node. """ method, url, body_json, params = self._prepare_structure_move( nodeIds=nodeIds, newParentNodeId=newParentNodeId @@ -1040,10 +1073,24 @@ def structure_move(self, nodeIds, *, newParentNodeId, auth=None): def structure_copy(self, nodeIds, *, newParentNodeId, auth=None): """ - Copy nodes specified by a list of UIDs ``nodeIds`` to a new parent node specified + Copy nodes specified by a list of UIDs ``nodeIds`` to the new parent node specified by ``newParentNodeId``. The API requires 'admin' priviledges. API: POST /copy + + Parameters + ---------- + nodeIds : list[str] + List of node unique IDs to be moved. + newParentNodeId : str + Unique ID of the new parent node. + auth : httpx.BasicAuth, optional + Object with authentication data (generated using ``auth_gen()`` method). + + Returns + ------- + dict + Dictionary with metadata for the new parent node. """ method, url, body_json, params = self._prepare_structure_copy( nodeIds=nodeIds, newParentNodeId=newParentNodeId @@ -1052,9 +1099,20 @@ def structure_copy(self, nodeIds, *, newParentNodeId, auth=None): def structure_path_get(self, uniqueNodeId): """ - Get path for the node with specified uniqueNodeId. + Get path for the node with specified uniqueNodeId. The path contains a sequence + of nodes starting from the root node. Node names are separated by '/' character. API: GET /path/{uniqueNodeId} + + Parameters + ---------- + uniqueNodeId : str + Unique ID of the node. + + Returns + ------- + str + Path of the node with names of nodes separated by '/' character. """ method, url = self._prepare_structure_path_get(uniqueNodeId=uniqueNodeId) return self.send_request(method, url) @@ -1063,9 +1121,20 @@ def structure_path_nodes(self, path): """ Get a list of nodes that match the specified path. The path can point to multiple nodes as long as node type is different (e.g. a folder and a configuration may have - the same name). + the same name and may be simultaneously present in the list). API: GET /path + + Parameters + ---------- + path : str + Path of the node with names of nodes separated by '/' character. + + Returns + ------- + list[dict] + List of nodes that match the specified path. Each node is represented as a dictionary + with node metadata as returned by the server. """ method, url, params = self._prepare_structure_path_nodes(path=path) return self.send_request(method, url, params=params) diff --git a/tests/common.py b/tests/common.py index c4d978f..e5a1f29 100644 --- a/tests/common.py +++ b/tests/common.py @@ -118,8 +118,9 @@ def _clear(): # Delete all filters filters = SR.filters_get() for f in filters: - if f.startswith("filter_prefix"): - SR.filter_delete(f["name"]) + f_name = f["name"] + if f_name.startswith(filter_prefix): + SR.filter_delete(f_name) _clear() yield