From b867fb4520259a2a199a65511bf46ecd1ed46986 Mon Sep 17 00:00:00 2001 From: Jessica Nash Date: Sun, 20 Aug 2023 06:11:28 -0700 Subject: [PATCH] add tabs in user guide and admin sections --- docs/requirements.txt | 1 + docs/source/admin_guide/setup.rst | 44 ++- docs/source/conf.py | 1 + docs/source/user_guide/client_setup.rst | 44 ++- .../source/user_guide/connecting_qcportal.rst | 107 ++--- docs/source/user_guide/datasets.rst | 372 +++++++++++------- docs/source/user_guide/metadata.rst | 20 +- docs/source/user_guide/molecule.rst | 168 ++++---- docs/source/user_guide/query_iterators.rst | 30 +- docs/source/user_guide/record_management.rst | 175 ++++---- 10 files changed, 588 insertions(+), 374 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 977ce60eb..843a17f4e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,6 +2,7 @@ sphinx_design pydata_sphinx_theme sphinx autodoc-pydantic +sphinx-copybutton -e ../qcportal -e ../qcfractalcompute -e ../qcfractal diff --git a/docs/source/admin_guide/setup.rst b/docs/source/admin_guide/setup.rst index 0df08132f..6632e19d8 100644 --- a/docs/source/admin_guide/setup.rst +++ b/docs/source/admin_guide/setup.rst @@ -23,10 +23,14 @@ The QCFractal server can be installed via `conda/anaconda `_. The packages exist under the `QCArchive organization `_ on Anaconda. -.. code-block:: bash +.. tab-set:: - $ conda create -n qcf_server qcfractal postgresql -c qcarchive/label/next - $ conda activate qcf_server + .. tab-item:: SHELL + + .. code-block:: bash + + conda create -n qcf_server qcfractal postgresql -c qcarchive/label/next + conda activate qcf_server Setting up the server @@ -35,10 +39,14 @@ Setting up the server You generally want to keep all files related to the QCFractal server in a single directory. So we are going to create a directory, and then initialize a configuration file there. -.. code-block:: bash +.. tab-set:: + + .. tab-item:: SHELL - $ mkdir qcf_server - $ qcfractal-server --config=qcf_server/qcf_config.yaml init-config + .. code-block:: bash + + mkdir qcf_server + qcfractal-server --config=qcf_server/qcf_config.yaml init-config This creates an example configuration file. You are now free to change those settings as needed - see :ref:`server_configuration`. @@ -57,25 +65,37 @@ Some fields are likely to be changed Now we are ready to initialize the database. This creates the database directory structure and files, as well as the actual postgres database and tables for QCFractal. -.. code-block:: bash +.. tab-set:: + + .. tab-item:: SHELL - $ qcfractal-server --config=qcf_server/qcf_config.yaml init-db + .. code-block:: bash + + qcfractal-server --config=qcf_server/qcf_config.yaml init-db Before starting the server, it doesn't hurt to check the configuration to make sure it matches your expectations. -.. code-block:: bash +.. tab-set:: + + .. tab-item:: SHELL + + .. code-block:: bash - $ qcfractal-server --config=qcf_server/qcf_config.yaml info + qcfractal-server --config=qcf_server/qcf_config.yaml info Now we may start the server! This will run the server in the foreground, so you can not use your terminal anymore. You can place it in the background with **screen** or any other utilities if needed. -.. code-block:: bash +.. tab-set:: + + .. tab-item:: SHELL + + .. code-block:: bash - $ qcfractal-server --config=qcf_server/qcf_config.yaml start + qcfractal-server --config=qcf_server/qcf_config.yaml start To stop a running server, you can use **Ctrl-C**. diff --git a/docs/source/conf.py b/docs/source/conf.py index 35eb62c17..c31feb7c2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,6 +46,7 @@ 'sphinx.ext.napoleon', 'sphinx_design', 'sphinxcontrib.autodoc_pydantic', + 'sphinx_copybutton' ] # Some options diff --git a/docs/source/user_guide/client_setup.rst b/docs/source/user_guide/client_setup.rst index 36c663084..7d6731560 100644 --- a/docs/source/user_guide/client_setup.rst +++ b/docs/source/user_guide/client_setup.rst @@ -8,10 +8,14 @@ The QCPortal package can be installed via `conda/anaconda `_. The packages exist under the `QCArchive organization `_ on Anaconda. -.. code-block:: bash +.. tab-set:: - $ conda create -n qcportal qcportal -c qcarchive/label/next - $ conda activate qcportal + .. tab-item:: SHELL + + .. code-block:: bash + + conda create -n qcportal qcportal -c qcarchive/label/next + conda activate qcportal .. _qcportal_setup_configfile: @@ -33,11 +37,15 @@ Single Server If you are only interested in a single server, then the configuration file can just contain the address and user information. -.. code-block:: yaml +.. tab-set:: + + .. tab-item:: CONFIG FILE + + .. code-block:: yaml - address: https://qcademo.molssi.org - username: your_username - password: Secret_Password + address: https://qcademo.molssi.org + username: your_username + password: Secret_Password Multiple Servers ~~~~~~~~~~~~~~~~ @@ -46,17 +54,21 @@ If you are working with multiple servers, then the configuration file contains s the address and other options. The name is arbitrary and is for the user to differentiate between different servers. -.. code-block:: yaml +.. tab-set:: - qca_demo_server: - address: https://qcademo.molssi.org - username: your_username - password: Secret_Password + .. tab-item:: CONFIG FILE + + .. code-block:: yaml - group_server: - address: http://192.168.123.123:7777 - username: your_username - password: Secret_Password + qca_demo_server: + address: https://qcademo.molssi.org + username: your_username + password: Secret_Password + + group_server: + address: http://192.168.123.123:7777 + username: your_username + password: Secret_Password The path to this file and the section name can passed to the :meth:`~qcportal.client.PortalClient.from_file` function. diff --git a/docs/source/user_guide/connecting_qcportal.rst b/docs/source/user_guide/connecting_qcportal.rst index e6cc03250..c7072a5c9 100644 --- a/docs/source/user_guide/connecting_qcportal.rst +++ b/docs/source/user_guide/connecting_qcportal.rst @@ -9,24 +9,36 @@ Connecting to the server is handled by the constructor of the :class:`~qcportal. The first parameter is the address or URI of the server you with to connect to (including ``http``/``https``). If no address is given, then by default the client will connect to the public, MolSSI-hosted server. - >>> from qcportal import PortalClient - >>> client = PortalClient() - >>> print(client.server_name) - MolSSI Public QCArchive Server +.. tab-set:: + + .. tab-item:: PYTHON + + >>> from qcportal import PortalClient + >>> client = PortalClient() + >>> print(client.server_name) + MolSSI Public QCArchive Server However, you can specify the address of another server. Here we connect to the MolSSI-hosted public demonstration server - >>> from qcportal import PortalClient - >>> client = PortalClient("https://qcademo.molssi.org") - >>> print(client.server_name) - MolSSI QCArchive Demo Server +.. tab-set:: + + .. tab-item:: PYTHON + + >>> from qcportal import PortalClient + >>> client = PortalClient("https://qcademo.molssi.org") + >>> print(client.server_name) + MolSSI QCArchive Demo Server Servers may require a username and password to connect or to perform certain actions; these can also be specified in the constructor. - >>> from qcportal import PortalClient - >>> client = PortalClient("https://my.qcarchive.server", username='grad_student_123', password='abc123XYZ') +.. tab-set:: + + .. tab-item:: PYTHON + + >>> from qcportal import PortalClient + >>> client = PortalClient("https://my.qcarchive.server", username='grad_student_123', password='abc123XYZ') For a description of the other parameters, see :class:`~qcportal.client.PortalClient`. @@ -44,18 +56,22 @@ To use this file, construct the client using the If no path is passed to this function, then the current working directory and then the ``~/.qca`` directory are search for ``qcportal_config.yaml``. -.. code-block:: py3 +.. tab-set:: - # A file containing a single server, file stored in working directory or ~/.qca - >>> from qcportal import PortalClient - >>> client = PortalClient.from_file() - >>> print(client.server_name) - MolSSI QCArchive Demo Server + .. tab-item:: PYTHON - # Manually specify a path to the config file - >>> client = PortalClient.from_file('group_server', '/path/to/config') - >>> print(client.server_name) - Professor's Group Server + .. code-block:: py3 + + # A file containing a single server, file stored in working directory or ~/.qca + >>> from qcportal import PortalClient + >>> client = PortalClient.from_file() + >>> print(client.server_name) + MolSSI QCArchive Demo Server + + # Manually specify a path to the config file + >>> client = PortalClient.from_file('group_server', '/path/to/config') + >>> print(client.server_name) + Professor's Group Server Viewing server metadata @@ -65,30 +81,33 @@ Some metadata about the server is stored in the client object. The metadata incl name and version, as well as limits on API calls. This also contains any Message-of-the-Day (MOTD) that the server administrator wishes to include. - -.. code-block:: py3 - - >>> from qcportal import PortalClient - >>> client = PortalClient('https://qcademo.molssi.org') - >>> print(client.server_info) - {'name': 'MolSSI QCArchive Demo Server', - 'manager_heartbeat_frequency': 1800, - 'version': '0.50b4.post4+ged0d0270', - 'api_limits': {'get_records': 1000, - 'add_records': 500, - 'get_dataset_entries': 2000, - 'get_molecules': 1000, - 'add_molecules': 1000, - 'get_managers': 1000, - 'manager_tasks_claim': 200, - 'manager_tasks_return': 10, - 'get_server_stats': 25, - 'get_access_logs': 1000, - 'get_error_logs': 100, - 'get_internal_jobs': 1000}, - 'client_version_lower_limit': '0', - 'client_version_upper_limit': '1', - 'motd': ''} +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 + + >>> from qcportal import PortalClient + >>> client = PortalClient('https://qcademo.molssi.org') + >>> print(client.server_info) + {'name': 'MolSSI QCArchive Demo Server', + 'manager_heartbeat_frequency': 1800, + 'version': '0.50b4.post4+ged0d0270', + 'api_limits': {'get_records': 1000, + 'add_records': 500, + 'get_dataset_entries': 2000, + 'get_molecules': 1000, + 'add_molecules': 1000, + 'get_managers': 1000, + 'manager_tasks_claim': 200, + 'manager_tasks_return': 10, + 'get_server_stats': 25, + 'get_access_logs': 1000, + 'get_error_logs': 100, + 'get_internal_jobs': 1000}, + 'client_version_lower_limit': '0', + 'client_version_upper_limit': '1', + 'motd': ''} Next steps diff --git a/docs/source/user_guide/datasets.rst b/docs/source/user_guide/datasets.rst index e52178427..a96d6023c 100644 --- a/docs/source/user_guide/datasets.rst +++ b/docs/source/user_guide/datasets.rst @@ -57,16 +57,19 @@ so two datasets can have the same name as long as they are of different types. T You can retrieve a dataset with via its ID with :meth:`~qcportal.client.PortalClient.get_dataset_by_id` and its name with :meth:`~qcportal.client.PortalClient.get_dataset` +.. tab-set:: -.. code-block:: py3 + .. tab-item:: PYTHON - >>> ds = client.get_dataset_by_id(123) - >>> print(ds.id, ds.dataset_type, ds.name) - 123 singlepoint Organic molecule energies + .. code-block:: py3 - >>> ds = client.get_dataset("optimization", "Diatomic geometries") - >>> print(ds.id, ds.dataset_type, ds.name) - 52 optimization Diatomic geometries + >>> ds = client.get_dataset_by_id(123) + >>> print(ds.id, ds.dataset_type, ds.name) + 123 singlepoint Organic molecule energies + + >>> ds = client.get_dataset("optimization", "Diatomic geometries") + >>> print(ds.id, ds.dataset_type, ds.name) + 52 optimization Diatomic geometries Adding Datasets @@ -75,11 +78,15 @@ Adding Datasets Datasets can be created on a server with the :meth:`~qcportal.client.PortalClient.add_dataset` function of the :class:`~qcportal.client.PortalClient`. This function returns the dataset: -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> ds = client.add_dataset("optimization", "Optimization of important molecules") - >>> print(ds.id) - 27 + >>> ds = client.add_dataset("optimization", "Optimization of important molecules") + >>> print(ds.id) + 27 The :meth:`~qcportal.client.PortalClient.add_dataset` takes several optional arguments, including some for descriptions of the dataset as well as default priority and :ref:`tags `. @@ -102,17 +109,21 @@ with :meth:`~qcportal.dataset_models.BaseDataset.set_name`, :meth:`~qcportal.dataset_models.BaseDataset.set_description`, :meth:`~qcportal.dataset_models.BaseDataset.set_metadata`, and so on. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> print(ds.description) - Optimization of diatomic molecules at different levels of theory + >>> print(ds.description) + Optimization of diatomic molecules at different levels of theory - >>> ds.set_description("A new description") + >>> ds.set_description("A new description") - >>> # It has been changed on the server - >>> ds = client.get_dataset_by_id(1) - >>> print(ds.description) - A new description + >>> # It has been changed on the server + >>> ds = client.get_dataset_by_id(1) + >>> print(ds.description) + A new description Status @@ -121,32 +132,40 @@ Status The :meth:`~qcportal.dataset_models.BaseDataset.status` returns a dictionary describing the status of the computations. This is indexed by specification -.. code-block:: py3 +.. tab-set:: - >>> ds.status() - {'pbe0/sto-3g': {: 4, - : 1}, - 'b3lyp/def2-tzvp': {: 1, - : 4}, - 'pbe/6-31g': {: 3, - : 2}} + .. tab-item:: PYTHON + + .. code-block:: py3 + + >>> ds.status() + {'pbe0/sto-3g': {: 4, + : 1}, + 'b3lyp/def2-tzvp': {: 1, + : 4}, + 'pbe/6-31g': {: 3, + : 2}} If you are in an interactive session or notebook, or just want a prettier version, you can use :meth:`~qcportal.dataset_models.BaseDataset.status_table` returns a table as a string, and :meth:`~qcportal.dataset_models.BaseDataset.print_status` prints a table of the statuses. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> ds.print_status_() - specification complete error invalid - ----------------- ---------- ------- --------- - pbe/def2-tzvp 3 2 - pbe/sto-3g 4 1 - pbe0/6-31g 4 1 - pbe0/6-31g** 4 1 - pbe0/aug-cc-pvtz 3 1 1 - pbe0/def2-tzvp 4 1 - pbe0/sto-3g 4 1 + .. code-block:: py3 + + >>> ds.print_status_() + specification complete error invalid + ----------------- ---------- ------- --------- + pbe/def2-tzvp 3 2 + pbe/sto-3g 4 1 + pbe0/6-31g 4 1 + pbe0/6-31g** 4 1 + pbe0/aug-cc-pvtz 3 1 1 + pbe0/def2-tzvp 4 1 + pbe0/sto-3g 4 1 .. note:: @@ -162,59 +181,78 @@ Specifications and Entries The specifications of the dataset are available with the ``.specification_names`` and ``.specifications`` properties. ``.specifications`` returns a dictionary, with the key being the name of the specification. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> print(ds.specification_names) - ['hf/sto-3g', 'hf/def2-tzvp'] + .. code-block:: py3 - >>> print(ds.specifications['hf/sto-3g']) - name='hf/sto-3g' specification=OptimizationSpecification(program='geometric', - qc_specification=QCSpecification(program='psi4', driver=, method='hf', - basis='sto-3g', keywords={'maxiter': 100}, protocols=AtomicResultProtocols(wavefunction=, - stdout=True, error_correction=ErrorCorrectionProtocol(default_policy=True, policies=None), - native_files=)), keywords={}, - protocols=OptimizationProtocols(trajectory=)) description=None + >>> print(ds.specification_names) + ['hf/sto-3g', 'hf/def2-tzvp'] + + >>> print(ds.specifications['hf/sto-3g']) + name='hf/sto-3g' specification=OptimizationSpecification(program='geometric', + qc_specification=QCSpecification(program='psi4', driver=, method='hf', + basis='sto-3g', keywords={'maxiter': 100}, protocols=AtomicResultProtocols(wavefunction=, + stdout=True, error_correction=ErrorCorrectionProtocol(default_policy=True, policies=None), + native_files=)), keywords={}, + protocols=OptimizationProtocols(trajectory=)) description=None Entries are slightly different. Since it is expected that a dataset may have many entries, only the names are accessible all at once +.. tab-set:: + + .. tab-item:: PYTHON -.. code-block:: py3 + .. code-block:: py3 - >>> print(ds.entry_names) - ['H2', 'N2', 'O2', 'F2', 'Hg2'] + >>> print(ds.entry_names) + ['H2', 'N2', 'O2', 'F2', 'Hg2'] You can obtain a full entry from its name with :meth:`~qcportal.dataset_models.BaseDataset.get_entry`: -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> print(ds.get_entry) - OptimizationDatasetEntry(name='H2', initial_molecule=Molecule(name='H2', formula='H2', hash='7746e69'), - additional_keywords={}, attributes={}, comment=None) + .. code-block:: py3 + + >>> print(ds.get_entry) + OptimizationDatasetEntry(name='H2', initial_molecule=Molecule(name='H2', formula='H2', hash='7746e69'), + additional_keywords={}, attributes={}, comment=None) If you need to get all entries, you may iterate over the entries with :meth:`~qcportal.dataset_models.BaseDataset.iterate_entries`: -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> for x in ds.iterate_entries(): - ... print(x.initial_molecule) - Molecule(name='H2', formula='H2', hash='7746e69') - Molecule(name='N2', formula='N2', hash='609abf3') - Molecule(name='O2', formula='O2', hash='018caee') - Molecule(name='F2', formula='F2', hash='7ffa835') - Molecule(name='Hg2', formula='Hg2', hash='a67cb93') + .. code-block:: py3 + + >>> for x in ds.iterate_entries(): + ... print(x.initial_molecule) + Molecule(name='H2', formula='H2', hash='7746e69') + Molecule(name='N2', formula='N2', hash='609abf3') + Molecule(name='O2', formula='O2', hash='018caee') + Molecule(name='F2', formula='F2', hash='7ffa835') + Molecule(name='Hg2', formula='Hg2', hash='a67cb93') :meth:`~qcportal.dataset_models.BaseDataset.iterate_entries` can also be restricted to only iterate over certain entry names. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> for x in ds.iterate_entries(entry_names=['H2', 'O2']): - ... print(x.initial_molecule) - Molecule(name='H2', formula='H2', hash='7746e69') - Molecule(name='O2', formula='O2', hash='018caee') + .. code-block:: py3 + + >>> for x in ds.iterate_entries(entry_names=['H2', 'O2']): + ... print(x.initial_molecule) + Molecule(name='H2', formula='H2', hash='7746e69') + Molecule(name='O2', formula='O2', hash='018caee') Retrieving Records @@ -223,44 +261,56 @@ Retrieving Records A single records can be retrieved by entry name and specification name with :meth:`~qcportal.dataset_models.BaseDataset.get_record` -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> rec = ds.get_record('H2', 'hf/sto-3g') - >>> print(rec) - + .. code-block:: py3 - >>> print(rec.final_molecule) - Molecule(name='H2', formula='H2', hash='6c7a0a9') + >>> rec = ds.get_record('H2', 'hf/sto-3g') + >>> print(rec) + + + >>> print(rec.final_molecule) + Molecule(name='H2', formula='H2', hash='6c7a0a9') Multiple records (or all records) can be obtained by using the iterator returned from :meth:`~qcportal.dataset_models.BaseDataset.iterate_records`. The iterator return a tuple of three values - the entry name, specification name, and then the full record. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> for e_name, s_name, record in ds.iterate_records(): - ... print(e_name, s_name, record.id, record.status) - H2 hf/sto-3g 3 RecordStatusEnum.complete - N2 hf/sto-3g 1 RecordStatusEnum.complete - O2 hf/sto-3g 4 RecordStatusEnum.complete - F2 hf/sto-3g 5 RecordStatusEnum.complete - Hg2 hf/sto-3g 2 RecordStatusEnum.error - H2 hf/def2-tzvp 8 RecordStatusEnum.complete - N2 hf/def2-tzvp 9 RecordStatusEnum.complete - O2 hf/def2-tzvp 6 RecordStatusEnum.complete + >>> for e_name, s_name, record in ds.iterate_records(): + ... print(e_name, s_name, record.id, record.status) + H2 hf/sto-3g 3 RecordStatusEnum.complete + N2 hf/sto-3g 1 RecordStatusEnum.complete + O2 hf/sto-3g 4 RecordStatusEnum.complete + F2 hf/sto-3g 5 RecordStatusEnum.complete + Hg2 hf/sto-3g 2 RecordStatusEnum.error + H2 hf/def2-tzvp 8 RecordStatusEnum.complete + N2 hf/def2-tzvp 9 RecordStatusEnum.complete + O2 hf/def2-tzvp 6 RecordStatusEnum.complete :meth:`~qcportal.dataset_models.BaseDataset.iterate_records` also has filtering options, including by entry name, specification name, and status -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> for e_name, s_name, record in ds.iterate_records(status='error'): - ... print(e_name, s_name, record.id, record.status) - Hg2 hf/sto-3g 2 RecordStatusEnum.error - Hg2 hf/def2-tzvp 10 RecordStatusEnum.error - Hg2 hf/6-31g 15 RecordStatusEnum.error - Hg2 hf/6-31g** 17 RecordStatusEnum.error + >>> for e_name, s_name, record in ds.iterate_records(status='error'): + ... print(e_name, s_name, record.id, record.status) + Hg2 hf/sto-3g 2 RecordStatusEnum.error + Hg2 hf/def2-tzvp 10 RecordStatusEnum.error + Hg2 hf/6-31g 15 RecordStatusEnum.error + Hg2 hf/6-31g** 17 RecordStatusEnum.error If the record was previously retrieved, it won't be retrieved again unless it has been updated on the server. This can be overridden with ``force_refetch=True`` which will always download a fresh record. @@ -281,54 +331,66 @@ When entries or specifications are added, the changes are reflected immediately First, we add some entries to this dataset. For an optimization dataset, an entry corresponds to an unoptimized 'initial' molecule. Adding entries returns :doc:`metadata `. -.. code-block:: py3 +.. tab-set:: - >>> from qcportal.molecules import Molecule - >>> from qcportal.optimization import OptimizationDatasetEntry + .. tab-item:: PYTHON - >>> mol = Molecule(symbols=['C', 'O'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) - >>> meta = ds.add_entry("carbon monoxide", mol) - >>> print(meta) - InsertMetadata(error_description=None, errors=[], inserted_idx=[0], existing_idx=[]) + .. code-block:: py3 - >>> # Can also create lots of entries and add them at once - >>> mol2 = Molecule(symbols=['F', 'F'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) - >>> mol3 = Molecule(symbols=['Br', 'Br'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) - >>> entry2 = OptimizationDatasetEntry(name='difluorine', initial_molecule=mol2) - >>> entry3 = OptimizationDatasetEntry(name='dibromine', initial_molecule=mol3) - >>> meta = ds.add_entries([entry2, entry3]) - >>> print(meta) - InsertMetadata(error_description=None, errors=[], inserted_idx=[0, 1], existing_idx=[]) + >>> from qcportal.molecules import Molecule + >>> from qcportal.optimization import OptimizationDatasetEntry + + >>> mol = Molecule(symbols=['C', 'O'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) + >>> meta = ds.add_entry("carbon monoxide", mol) + >>> print(meta) + InsertMetadata(error_description=None, errors=[], inserted_idx=[0], existing_idx=[]) + + >>> # Can also create lots of entries and add them at once + >>> mol2 = Molecule(symbols=['F', 'F'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) + >>> mol3 = Molecule(symbols=['Br', 'Br'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) + >>> entry2 = OptimizationDatasetEntry(name='difluorine', initial_molecule=mol2) + >>> entry3 = OptimizationDatasetEntry(name='dibromine', initial_molecule=mol3) + >>> meta = ds.add_entries([entry2, entry3]) + >>> print(meta) + InsertMetadata(error_description=None, errors=[], inserted_idx=[0, 1], existing_idx=[]) Now our dataset has three entries -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> print(ds.entry_names) - ['carbon monoxide', 'difluorine', 'dibromine'] + .. code-block:: py3 + + >>> print(ds.entry_names) + ['carbon monoxide', 'difluorine', 'dibromine'] Next, we will add some specifications. For an optimization dataset, this is an :class:`~qcportal.optimization.OptimizationSpecification`. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> from qcportal.singlepoint import SinglepointSpecification - >>> from qcportal.optimization import OptimizationSpecification + .. code-block:: py3 - >>> # Use geometric, compute gradients with psi4. Optimize with b3lyp/def2-tzvp - >>> spec = OptimizationSpecification( - ... program='geometric', - ... qc_specification=QCSpecification( - ... program='psi4', - ... driver='deferred', - ... method='b3lyp', - ... basis='def2-tzvp', - ... ) - ... ) + >>> from qcportal.singlepoint import SinglepointSpecification + >>> from qcportal.optimization import OptimizationSpecification - >>> meta = ds.add_specification(name='psi4/b3lyp/def2-tzvp', specification=spec) - >>> print(meta) - InsertMetadata(error_description=None, errors=[], inserted_idx=[0], existing_idx=[]) + >>> # Use geometric, compute gradients with psi4. Optimize with b3lyp/def2-tzvp + >>> spec = OptimizationSpecification( + ... program='geometric', + ... qc_specification=QCSpecification( + ... program='psi4', + ... driver='deferred', + ... method='b3lyp', + ... basis='def2-tzvp', + ... ) + ... ) + + >>> meta = ds.add_specification(name='psi4/b3lyp/def2-tzvp', specification=spec) + >>> print(meta) + InsertMetadata(error_description=None, errors=[], inserted_idx=[0], existing_idx=[]) .. _dataset_submission: @@ -343,15 +405,19 @@ With no arguments, this will create missing records for all entries and specific default tag and priority of the dataset. However, you may also submit only certain entries and specifications, or change the tag and priority. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> ds.submit() # Create everything + >>> ds.submit() # Create everything - >>> # Submit missing difluorine computations with a special tag - >>> ds.submit(['difluorine'], tag='special_tag') + >>> # Submit missing difluorine computations with a special tag + >>> ds.submit(['difluorine'], tag='special_tag') - >>> # Submit dibromine hf/sto-3g computation at a high priority - >>> ds.submit(['dibromine'], ['hf/sto-3g'], priority='high') + >>> # Submit dibromine hf/sto-3g computation at a high priority + >>> ds.submit(['dibromine'], ['hf/sto-3g'], priority='high') Renaming and Deleting Entries and Specifications @@ -363,25 +429,33 @@ Entries and specifications can be renamed and deleted. Deletion can also optiona :meth:`~qcportal.dataset_models.BaseDataset.rename_specification` take a dictionary of old name to new name -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> ds.rename_entries({'difluorine': 'F2 molecule'}) - >>> ent = ds.get_entry('F2 molecule') - >>> print(ent.initial_molecule) - initial_molecule=Molecule(name='F2', formula='F2', hash='7ffa835') + >>> ds.rename_entries({'difluorine': 'F2 molecule'}) + >>> ent = ds.get_entry('F2 molecule') + >>> print(ent.initial_molecule) + initial_molecule=Molecule(name='F2', formula='F2', hash='7ffa835') Entries and specifications are deleted with :meth:`~qcportal.dataset_models.BaseDataset.delete_entries` and :meth:`~qcportal.dataset_models.BaseDataset.delete_specification`. Note that deleting entries and specifications by default do not delete the records -.. code-block:: py3 +.. tab-set:: - >>> # Keeps any records, but removes from dataset - >>> ds.delete_entries(['carbon monoxide']) + .. tab-item:: PYTHON - >>> # Deletes the records too - >>> ds.delete_specification('hf/sto-3g', delete_records=True) + .. code-block:: py3 + + >>> # Keeps any records, but removes from dataset + >>> ds.delete_entries(['carbon monoxide']) + + >>> # Deletes the records too + >>> ds.delete_specification('hf/sto-3g', delete_records=True) Record Management @@ -400,10 +474,14 @@ These functions are similar to the client counterparts, but instead use entry an In addition, individual records can be removed from a dataset (and optionally deleted) with :meth:`~qcportal.dataset_models.BaseDataset.remove_records`. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> # Reset carbon monoxide records - >>> ds.reset_records(entry_names=['carbon monoxide']) + >>> # Reset carbon monoxide records + >>> ds.reset_records(entry_names=['carbon monoxide']) - >>> # Cancel pbe0/def2-qzvp computations - >>> ds.cancel_records(specification_names=['pbe0/def2-qzvp']) \ No newline at end of file + >>> # Cancel pbe0/def2-qzvp computations + >>> ds.cancel_records(specification_names=['pbe0/def2-qzvp']) \ No newline at end of file diff --git a/docs/source/user_guide/metadata.rst b/docs/source/user_guide/metadata.rst index 72df54e4e..e46a1f95e 100644 --- a/docs/source/user_guide/metadata.rst +++ b/docs/source/user_guide/metadata.rst @@ -11,17 +11,21 @@ then ``meta.success`` will be ``True``. If any errors happened, these are also s Metadata classes also have fields with an ``_idx`` suffix. These are lists that reference the `zero-based index` of the input list. If we take molecules as an example, -.. code-block:: py3 +.. tab-set:: - >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - >>> nitrogen = Molecule(symbols=['N', 'N'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) - >>> meta, ids = client.add_molecules([water, nitrogen]) + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> print(meta.success) - True + >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + >>> nitrogen = Molecule(symbols=['N', 'N'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0]) + >>> meta, ids = client.add_molecules([water, nitrogen]) - >>> print(meta) - InsertMetadata(error_description=None, errors=[], inserted_idx=[0], existing_idx=[1]) + >>> print(meta.success) + True + + >>> print(meta) + InsertMetadata(error_description=None, errors=[], inserted_idx=[0], existing_idx=[1]) In this case, the addition of the two molecules to the server was a success. The first molecule ``water`` did not exist and was added to the server - this is represented by ``0`` in the ``inserted_idx`` attribute diff --git a/docs/source/user_guide/molecule.rst b/docs/source/user_guide/molecule.rst index 13dc51ca1..b595eeedd 100644 --- a/docs/source/user_guide/molecule.rst +++ b/docs/source/user_guide/molecule.rst @@ -12,38 +12,50 @@ Creating Molecules A common way to programmatically create a molecule is with the Molecule constructor. With the constructor, you typically specify the symbols and geometry (coordinates) in bohr: -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> from qcportal.molecules import Molecule - >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[[0.0, 2.0, 0.0], [2.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) - >>> print(water) - Molecule(name='H2O', formula='H2O', hash='3a04ba3') + .. code-block:: py3 + + >>> from qcportal.molecules import Molecule + >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[[0.0, 2.0, 0.0], [2.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) + >>> print(water) + Molecule(name='H2O', formula='H2O', hash='3a04ba3') The geometry can be specified as a nested list as above, or simply as a flattened list. The below is equivalent to the above: -.. code-block:: py3 +.. tab-set:: - >>> from qcportal.molecules import Molecule - >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - >>> print(water) - Molecule(name='H2O', formula='H2O', hash='3a04ba3') + .. tab-item:: PYTHON + + .. code-block:: py3 + + >>> from qcportal.molecules import Molecule + >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + >>> print(water) + Molecule(name='H2O', formula='H2O', hash='3a04ba3') Another option is to read molecules from strings that are in some common format (like XYZ, although it will work with more): -.. code-block:: py3 +.. tab-set:: - >>> mol_xyz = """ - ... 3 - ... - ... H 0.000000 1.000000 0.000000 - ... H 1.000000 0.000000 0.000000 - ... O 0.000000 0.000000 0.000000 - ... """ - >>> water = Molecule.from_data(mol_xyz) - >>> print(water2) - Molecule(name='H2O', formula='H2O', hash='246998b') + .. tab-item:: PYTHON + + .. code-block:: py3 + + >>> mol_xyz = """ + ... 3 + ... + ... H 0.000000 1.000000 0.000000 + ... H 1.000000 0.000000 0.000000 + ... O 0.000000 0.000000 0.000000 + ... """ + >>> water = Molecule.from_data(mol_xyz) + >>> print(water2) + Molecule(name='H2O', formula='H2O', hash='246998b') Note that in the case of XYZ files, the units are angstroms. @@ -69,16 +81,20 @@ Adding molecules to the server Molecules can be added to the server database with `~qcportal.client.PortalClient.add_molecules`. This returns some metadata about the insertion, and the molecule IDs. -.. code-block:: py3 +.. tab-set:: - >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - >>> water2 = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.5, 0.0, 2.5, 0.0, 0.0, 0.0, 0.0, 0.0]) - >>> meta, ids = client.add_molecules([water, water2]) - >>> print(meta) - InsertMetadata(error_description=None, errors=[], inserted_idx=[0, 1], existing_idx=[]) + .. tab-item:: PYTHON - >>> print(ids) - [585, 586] + .. code-block:: py3 + + >>> water = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + >>> water2 = Molecule(symbols=['H', 'H', 'O'], geometry=[0.0, 2.5, 0.0, 2.5, 0.0, 0.0, 0.0, 0.0, 0.0]) + >>> meta, ids = client.add_molecules([water, water2]) + >>> print(meta) + InsertMetadata(error_description=None, errors=[], inserted_idx=[0, 1], existing_idx=[]) + + >>> print(ids) + [585, 586] These IDs can be passed into functions like :meth:`~qcportal.client.PortalClient.add_singlepoints` instead of full molecule objects. @@ -90,19 +106,27 @@ The client has two methods for retrieving molecules: :meth:`~qcportal.client.Por :meth:`~qcportal.client.PortalClient.query_molecules`. The :meth:`~qcportal.client.PortalClient.get_molecules` method is used to get molecules by ID, and returns molecules in the same order as the given ids. -.. code-block:: py3 +.. tab-set:: - >>> mols = client.get_molecules([5, 15, 10]) - >>> print(mols[0].id, mols[1].id, mols[2].id) - 5 15 10 + .. tab-item:: PYTHON + + .. code-block:: py3 + + >>> mols = client.get_molecules([5, 15, 10]) + >>> print(mols[0].id, mols[1].id, mols[2].id) + 5 15 10 You can also specify a single ID and get a single molecule back -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> mol = client.get_molecules(5) - >>> print(mol.id) - 5 + .. code-block:: py3 + + >>> mol = client.get_molecules(5) + >>> print(mol.id) + 5 You can also query the molecules in the database with :meth:`~qcportal.client.PortalClient.query_molecules`. @@ -110,13 +134,17 @@ This function returns an :doc:`iterator `, which you can then u iterate over the results. The iterator automatically handles returning batches or pages of query results from the server. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON - >>> query_it = client.query_molecules(molecular_formula='N2') - >>> for mol in query_it: - ... print(mol.id, mol.identifiers.molecular_formula) - 371 N2 - 372 N2 + .. code-block:: py3 + + >>> query_it = client.query_molecules(molecular_formula='N2') + >>> for mol in query_it: + ... print(mol.id, mol.identifiers.molecular_formula) + 371 N2 + 372 N2 .. caution:: @@ -131,11 +159,15 @@ Managing Molecules Molecules can be deleted from the server with :meth:`~qcportal.client.PortalClient.delete_molecules` -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> meta = client.delete_molecules([585]) - >>> print(meta) - DeleteMetadata(error_description=None, errors=[], deleted_idx=[0], n_children_deleted=0) + >>> meta = client.delete_molecules([585]) + >>> print(meta) + DeleteMetadata(error_description=None, errors=[], deleted_idx=[0], n_children_deleted=0) The server also allows for some limited modification of molecules. This is limited to the name, comment, and @@ -143,28 +175,32 @@ identifiers of the molecule. By default, new identifiers will be merged with the unless ``overwrite_identifiers=True``, in which case all identifiers will be replaced (that is, identifiers that are not specified in the call to ``modify_molecules`` will be removed). -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> n2 = Molecule(symbols=['N', 'N'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0], - ... name='nitrogen', comment='initial geometry of nitrogen', identifiers={'smiles': 'N#N'}) - >>> _, ids = client.add_molecules([n2]) - >>> print(ids) - [601] + >>> n2 = Molecule(symbols=['N', 'N'], geometry=[0.0, 0.0, 0.0, 0.0, 0.0, 2.0], + ... name='nitrogen', comment='initial geometry of nitrogen', identifiers={'smiles': 'N#N'}) + >>> _, ids = client.add_molecules([n2]) + >>> print(ids) + [601] - >>> meta = client.modify_molecule(601, name='dinitrogen', comment='dinitrogen molecule', - ... identifiers={'pubchem_cid': '947'}) - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + >>> meta = client.modify_molecule(601, name='dinitrogen', comment='dinitrogen molecule', + ... identifiers={'pubchem_cid': '947'}) + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) - >>> mol = client.get_molecules(601) - >>> print(mol.name) - dinitrogen + >>> mol = client.get_molecules(601) + >>> print(mol.name) + dinitrogen - >>> print(mol.comment) - dinitrogen molecule + >>> print(mol.comment) + dinitrogen molecule - >>> print(mol.identifiers.smiles) - N#N + >>> print(mol.identifiers.smiles) + N#N - >>> print(mol.identifiers.pubchem_cid) - 947 + >>> print(mol.identifiers.pubchem_cid) + 947 diff --git a/docs/source/user_guide/query_iterators.rst b/docs/source/user_guide/query_iterators.rst index bac30f703..8247c432f 100644 --- a/docs/source/user_guide/query_iterators.rst +++ b/docs/source/user_guide/query_iterators.rst @@ -9,23 +9,31 @@ The reason for returning an iterator rather than a list is that an iterator will a query can return many items, they must be retrieved in batches. The iterator does this automatically, but does so incrementally as iteration continues. -.. code-block:: py3 +.. tab-set:: - >>> query_it = client.query_molecules(molecular_formula='N2') - >>> for mol in query_it: - ... print(mol.id, mol.identifiers.molecular_formula) - 371 N2 - 372 N2 + .. tab-item:: PYTHON + + .. code-block:: py3 + + >>> query_it = client.query_molecules(molecular_formula='N2') + >>> for mol in query_it: + ... print(mol.id, mol.identifiers.molecular_formula) + 371 N2 + 372 N2 If you need all items as a list, then you can use the ``list`` constructor, which will use the iterator to fill in the list. In this case, all the records will be fetched from the server as the list is being created. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> query_it = client.query_molecules(molecular_formula='N2') - >>> mols = list(query_it) - >>> print(len(mols)) - 621 + >>> query_it = client.query_molecules(molecular_formula='N2') + >>> mols = list(query_it) + >>> print(len(mols)) + 621 Iterators API diff --git a/docs/source/user_guide/record_management.rst b/docs/source/user_guide/record_management.rst index b06baf3fe..3c1251518 100644 --- a/docs/source/user_guide/record_management.rst +++ b/docs/source/user_guide/record_management.rst @@ -115,25 +115,33 @@ You can check what the error is with the :attr:`~qcportal.record_models.BaseRecord.error`, :attr:`~qcportal.record_models.BaseRecord.stdout`, and :attr:`~qcportal.record_models.BaseRecord.stderr` properties. -.. code-block:: py3 +.. tab-set:: - >>> r = client.get_records(411) - >>> print(r.status) - RecordStatusEnum.error + .. tab-item:: PYTHON - >>> meta = client.reset_records(411) # can also take a list - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + .. code-block:: py3 + + >>> r = client.get_records(411) + >>> print(r.status) + RecordStatusEnum.error + + >>> meta = client.reset_records(411) # can also take a list + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) The :doc:`metadata ` is similarly to the metadata returned by ``add_`` functions. In this case, the only record (index 0) had its status updated back to ``waiting`` and will be picked up/run by a manager again. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> r = client.get_records(411) - >>> print(r.status) - RecordStatusEnum.waiting + >>> r = client.get_records(411) + >>> print(r.status) + RecordStatusEnum.waiting Deleting records @@ -150,20 +158,23 @@ If ``soft_delete=False`` ("hard delete"), then the record is deleted permanently A record cannot be hard-deleted if it is being referenced somewhere (another record or a dataset). +.. tab-set:: -.. code-block:: py3 + .. tab-item:: PYTHON - >>> r = client.get_records(149) - >>> print(r.status) - RecordStatusEnum.complete + .. code-block:: py3 - >>> meta = client.delete_records(149, soft_delete=True) # can also take a list - >>> print(meta) - DeleteMetadata(error_description=None, errors=[], deleted_idx=[0], n_children_deleted=5) + >>> r = client.get_records(149) + >>> print(r.status) + RecordStatusEnum.complete - >>> r = client.get_records(149) - >>> print(r.status) - RecordStatusEnum.deleted + >>> meta = client.delete_records(149, soft_delete=True) # can also take a list + >>> print(meta) + DeleteMetadata(error_description=None, errors=[], deleted_idx=[0], n_children_deleted=5) + + >>> r = client.get_records(149) + >>> print(r.status) + RecordStatusEnum.deleted Note that in this example, the child records were also (soft) deleted. This can be controlled with the ``delete_children`` argument to :meth:`~qcportal.client.PortalClient.delete_records`. @@ -171,23 +182,31 @@ In this case, the record was an optimization, meaning that the trajectory record We can undo a soft deletion with :meth:`~qcportal.client.PortalClient.undelete_records` -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> meta = client.undelete_records(149) # can also take a list - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=5) + >>> meta = client.undelete_records(149) # can also take a list + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=5) Hard deletions are permanent and result in the removal of the record from the server -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> meta = client.delete_records([942, 943], soft_delete=False) # can also take a list - >>> print(meta) - DeleteMetadata(error_description=None, errors=[], deleted_idx=[0, 1], n_children_deleted=0) + >>> meta = client.delete_records([942, 943], soft_delete=False) # can also take a list + >>> print(meta) + DeleteMetadata(error_description=None, errors=[], deleted_idx=[0, 1], n_children_deleted=0) - >>> r = client.get_records(942, missing_ok=True) - >>> print(r) - None + >>> r = client.get_records(942, missing_ok=True) + >>> print(r) + None .. warning:: @@ -207,19 +226,23 @@ before it was cancelled, with will go back to a ``waiting state``. Invalidation can be undone with :meth:`~qcportal.client.PortalClient.uncancel_records`. -.. code-block:: py3 +.. tab-set:: - >>> r = client.get_records(411) - >>> print(r.status) - RecordStatusEnum.waiting + .. tab-item:: PYTHON - >>> meta = client.cancel_records(411) # can also take a list - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + .. code-block:: py3 - >>> r = client.get_records(411) - >>> print(r.status) - RecordStatusEnum.cancelled + >>> r = client.get_records(411) + >>> print(r.status) + RecordStatusEnum.waiting + + >>> meta = client.cancel_records(411) # can also take a list + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + + >>> r = client.get_records(411) + >>> print(r.status) + RecordStatusEnum.cancelled .. note:: @@ -234,19 +257,23 @@ the record should be deleted, but in some cases it may be useful to keep the rec Invalidation can be undone with :meth:`~qcportal.client.PortalClient.uninvalidate_records`. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> r = client.get_records(149) - >>> print(r.status) - RecordStatusEnum.complete + >>> r = client.get_records(149) + >>> print(r.status) + RecordStatusEnum.complete - >>> meta = client.invalidate_records(149) # can also take a list - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + >>> meta = client.invalidate_records(149) # can also take a list + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) - >>> r = client.get_records(149) - >>> print(r.status) - RecordStatusEnum.invalid + >>> r = client.get_records(149) + >>> print(r.status) + RecordStatusEnum.invalid .. note:: @@ -259,19 +286,23 @@ Changing tag and priority A record's tag and priority can be changed if it has not yet been successfully completed (ie, the task or service still exists - see :doc:`../overview/tasks_services`). -.. code-block:: py3 +.. tab-set:: - >>> r = client.get_records(941) - >>> print(r.task.tag, r.task.priority) - * PriorityEnum.normal + .. tab-item:: PYTHON - >>> meta = client.modify_records(941, new_tag='a_new_tag', new_priority='low') - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + .. code-block:: py3 - >>> r = client.get_records(941) - >>> print(r.task.tag, r.task.priority) - a_new_tag PriorityEnum.low + >>> r = client.get_records(941) + >>> print(r.task.tag, r.task.priority) + * PriorityEnum.normal + + >>> meta = client.modify_records(941, new_tag='a_new_tag', new_priority='low') + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + + >>> r = client.get_records(941) + >>> print(r.task.tag, r.task.priority) + a_new_tag PriorityEnum.low Adding comments @@ -282,13 +313,17 @@ with the :attr:`~qcportal.record_models.BaseRecord.comments` property. The server will automatically add the time the comment was added and the name of the user adding the comment. -.. code-block:: py3 +.. tab-set:: + + .. tab-item:: PYTHON + + .. code-block:: py3 - >>> meta = client.add_comment(149, 'Invalid due to convergence to wrong minimum') - >>> print(meta) - UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) + >>> meta = client.add_comment(149, 'Invalid due to convergence to wrong minimum') + >>> print(meta) + UpdateMetadata(error_description=None, errors=[], updated_idx=[0], n_children_updated=0) - >>> r = client.get_records(149) - >>> print(r.comments) - [RecordComment(id=1, record_id=149, username='ben', timestamp=datetime.datetime(2023, 1, 4, 17, 21, 1, 990674), - comment='Invalid due to convergence to wrong minimum')] \ No newline at end of file + >>> r = client.get_records(149) + >>> print(r.comments) + [RecordComment(id=1, record_id=149, username='ben', timestamp=datetime.datetime(2023, 1, 4, 17, 21, 1, 990674), + comment='Invalid due to convergence to wrong minimum')] \ No newline at end of file