diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index da8255dfa..4cc846a65 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,8 +2,6 @@ ## Changes -## Tests - ## Known Issues ## Notes @@ -12,3 +10,4 @@ - [ ] My name is on the list of contributors (`CONTRIBUTORS.md`) in the pull request source branch. - [ ] I have updated the documentation to reflect my changes. +- [ ] My code changes have been verified by automated tests and pass all relevant test scenarios. diff --git a/.github/workflows/build_and_deploy_docs.yaml b/.github/workflows/build_and_deploy_docs.yaml index 53bf859b9..fa4587e3f 100644 --- a/.github/workflows/build_and_deploy_docs.yaml +++ b/.github/workflows/build_and_deploy_docs.yaml @@ -16,8 +16,8 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 with: python-version: 3.x - run: pip install -r requirements_docs.txt diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b3ab09f8a..0d75843f2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/docs_check.yaml b/.github/workflows/docs_check.yaml index f16926d87..649437cb0 100644 --- a/.github/workflows/docs_check.yaml +++ b/.github/workflows/docs_check.yaml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set Up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.11 diff --git a/.github/workflows/doctest.yaml b/.github/workflows/doctest.yaml new file mode 100644 index 000000000..ef5d93dde --- /dev/null +++ b/.github/workflows/doctest.yaml @@ -0,0 +1,50 @@ +# check documentation code example with pytest doctest and gives an error if a code example is wrong + +name: Doctest +permissions: read-all + +on: + push: + branches: + - main + - develop + - trunk-merge/** + pull_request: + branches: + - main + - develop + +jobs: + doctest: + strategy: + matrix: + python-version: [3.12] + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + env: + CRIPT_HOST: https://lb-stage.mycriptapp.org/ + CRIPT_TOKEN: 123456789 + CRIPT_STORAGE_TOKEN: 987654321 + CRIPT_TESTS: False + + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Check out code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + python${{ matrix.python-version }} -m pip install --upgrade pip + pip install -r requirements_dev.txt + + - name: pip install CRIPT Python SDK local package + run: python${{ matrix.python-version }} -m pip install -e . + + - name: Run Doctests + run: python${{ matrix.python-version }} -m pytest --doctest-modules src/cript/ diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 5761592cf..2df11ab3b 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -18,19 +18,19 @@ jobs: mypy-test: strategy: matrix: - python-version: [3.11] + python-version: [3.12] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install dependencies run: | diff --git a/.github/workflows/test_coverage.yaml b/.github/workflows/test_coverage.yaml index 48d46a114..27b7d1af2 100644 --- a/.github/workflows/test_coverage.yaml +++ b/.github/workflows/test_coverage.yaml @@ -21,7 +21,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.11] + python-version: [3.12] env: CRIPT_HOST: https://lb-stage.mycriptapp.org/ @@ -30,10 +30,10 @@ jobs: CRIPT_TESTS: False steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.11 diff --git a/.github/workflows/test_examples.yml b/.github/workflows/test_examples.yml index 14a1dca96..de9db832a 100644 --- a/.github/workflows/test_examples.yml +++ b/.github/workflows/test_examples.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: [3.11] + python-version: [3.12] env: CRIPT_HOST: https://lb-stage.mycriptapp.org/ @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f72ae6dc0..d7f433397 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] - python-version: [3.7, 3.11] + python-version: [3.8, 3.12] env: CRIPT_HOST: https://lb-stage.mycriptapp.org/ @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/trunk.yml b/.github/workflows/trunk.yml index 5dd01e2ad..287013531 100644 --- a/.github/workflows/trunk.yml +++ b/.github/workflows/trunk.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Trunk Check uses: trunk-io/trunk-action@v1 diff --git a/.trunk/configs/.cspell.json b/.trunk/configs/.cspell.json index 925a5edf9..e236b70be 100644 --- a/.trunk/configs/.cspell.json +++ b/.trunk/configs/.cspell.json @@ -117,6 +117,10 @@ "setuptools", "miniconda", "pymdown", - "BCDB" + "BCDB", + "doctest", + "Doctest", + "Doctests", + "linenums" ] } diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 580fd9404..18ba66027 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,29 +1,29 @@ version: 0.1 cli: - version: 1.14.2 + version: 1.15.0 plugins: sources: - id: trunk - ref: v1.2.2 + ref: v1.2.3 uri: https://github.com/trunk-io/plugins lint: enabled: - bandit@1.7.5 - checkov@2.4.9 - - osv-scanner@1.3.6 - - trivy@0.44.1 - - trufflehog@3.54.0 + - osv-scanner@1.4.0 + - trivy@0.45.0 + - trufflehog@3.56.0 - svgo@3.0.2 - - cspell@7.1.1 + - cspell@7.3.6 - actionlint@1.6.25 - - black@23.7.0 + - black@23.9.1 - git-diff-check - gitleaks@8.18.0 - isort@5.12.0 - - markdownlint@0.35.0 + - markdownlint@0.36.0 - oxipng@8.0.0 - - prettier@3.0.2 - - ruff@0.0.286 + - prettier@3.0.3 + - ruff@0.0.289 - taplo@0.8.1 - yamllint@1.32.0 ignore: @@ -40,7 +40,7 @@ lint: runtimes: enabled: - - go@1.19.5 + - go@1.21.0 - node@18.12.1 - python@3.10.8 actions: diff --git a/README.md b/README.md index 0b95c19d1..ef855d800 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![License](./CRIPT_full_logo_colored_transparent.png)](https://github.com/C-Accel-CRIPT/Python-SDK/blob/develop/LICENSE.md) [![License](https://img.shields.io/github/license/C-Accel-CRIPT/cript?style=flat-square)](https://github.com/C-Accel-CRIPT/Python-SDK/blob/develop/LICENSE.md) -[![Python](https://img.shields.io/badge/Language-Python%203.7+-blue?style=flat-square&logo=python)](https://www.python.org/) +[![Python](https://img.shields.io/badge/Language-Python%203.8+-blue?style=flat-square&logo=python)](https://www.python.org/) [![Code style is black](https://img.shields.io/badge/Code%20Style-black-000000.svg?style=flat-square&logo=python)](https://github.com/psf/black) [![Link to CRIPT website](https://img.shields.io/badge/platform-criptapp.org-blueviolet?style=flat-square)](https://criptapp.org/) [![Using Pytest](https://img.shields.io/badge/Dependencies-pytest-green?style=flat-square&logo=Pytest)](https://docs.pytest.org/en/7.2.x/) @@ -36,7 +36,7 @@ The CRIPT Python SDK allows programmatic access to the [CRIPT platform](https:// ## Installation -CRIPT Python SDK requires Python 3.7+ +CRIPT Python SDK requires Python 3.8+ The latest released of CRIPT Python SDK is available on [Python Package Index (PyPI)](https://pypi.org/project/cript/) @@ -48,7 +48,9 @@ pip install cript ## Documentation -To learn more about the CRIPT Python SDK please check the [CRIPT-SDK documentation](https://c-accel-cript.github.io/Python-SDK/) +To learn more about the CRIPT Python SDK please check the [CRIPT Python SDK user documentation](https://c-accel-cript.github.io/Python-SDK/) + +To learn more about the internal workings of the CRIPT Python SDK please check the [CRIPT Python SDK internal documentation](https://github.com/C-Accel-CRIPT/Python-SDK/wiki) --- diff --git a/tests/conftest.py b/conftest.py similarity index 86% rename from tests/conftest.py rename to conftest.py index 66c2da3ca..51081d5fb 100644 --- a/tests/conftest.py +++ b/conftest.py @@ -11,11 +11,12 @@ import os import pytest -from fixtures.primary_nodes import * -from fixtures.subobjects import * -from fixtures.supporting_nodes import * import cript +from tests.fixtures.api_fixtures import * +from tests.fixtures.primary_nodes import * +from tests.fixtures.subobjects import * +from tests.fixtures.supporting_nodes import * def _get_cript_tests_env() -> bool: @@ -48,8 +49,6 @@ def cript_api(): """ storage_token = os.getenv("CRIPT_STORAGE_TOKEN") - assert cript.api.api._global_cached_api is None - with cript.API(host=None, api_token=None, storage_token=storage_token) as api: # overriding AWS S3 cognito variables to be sure we do not upload test data to production storage # staging AWS S3 cognito storage variables @@ -61,4 +60,7 @@ def cript_api(): yield api - assert cript.api.api._global_cached_api is None + +@pytest.fixture(autouse=True) +def inject_doctest_namespace(doctest_namespace, cript_api): + doctest_namespace["api"] = cript_api diff --git a/docs/examples/simulation.md b/docs/examples/simulation.md index 5ab9580e0..b49cb65bc 100644 --- a/docs/examples/simulation.md +++ b/docs/examples/simulation.md @@ -55,8 +55,11 @@ with cript.API(host="https://api.criptapp.org/", api_token="123456", storage_tok Here in a jupyter notebook, we need to connect manually. We just have to remember to disconnect at the end. ```python -api = cript.API(host="https://api.criptapp.org/", api_token=None, storage_token="123456") +api = cript.API( + host="https://api.criptapp.org/", api_token=None, storage_token="123456" +) api = api.connect() + ``` ## Create a Project @@ -121,9 +124,17 @@ If They are not, you can create them as follows: ```python python = cript.Software(name="python", version="3.9") + rdkit = cript.Software(name="rdkit", version="2020.9") -stage = cript.Software(name="stage", source="https://doi.org/10.1021/jp505332p", version="N/A") -packmol = cript.Software(name="Packmol", source="http://m3g.iqm.unicamp.br/packmol", version="N/A") + +stage = cript.Software( + name="stage", source="https://doi.org/10.1021/jp505332p", version="N/A" +) + +packmol = cript.Software( + name="Packmol", source="http://m3g.iqm.unicamp.br/packmol", version="N/A" +) + openmm = cript.Software(name="openmm", version="7.5") ``` @@ -195,8 +206,11 @@ init = cript.Computation( ) # Initiate the simulation equilibration using a separate node. -# The equilibration process is governed by specific conditions and a set equilibration time. -# Given this is an NPT (Number of particles, Pressure, Temperature) simulation, conditions such as the number of chains, temperature, and pressure are specified. +# The equilibration process is governed by specific conditions and a +# set equilibration time. +# Given this is an NPT (Number of particles, Pressure, Temperature) +# simulation, conditions such as the number of chains, temperature, +# and pressure are specified. equilibration = cript.Computation( name="Equilibrate data prior to measurement", type="MD", @@ -211,7 +225,8 @@ equilibration = cript.Computation( ) # This section involves the actual data measurement. -# Note that we use the previously computed data as a prerequisite. Additionally, we incorporate the input data at a later stage. +# Note that we use the previously computed data as a prerequisite. +# Additionally, we incorporate the input data at a later stage. bulk = cript.Computation( name="Bulk simulation for measurement", type="MD", @@ -225,7 +240,8 @@ bulk = cript.Computation( prerequisite_computation=equilibration, ) -# The following step involves analyzing the data from the measurement run to ascertain a specific property. +# The following step involves analyzing the data +# from the measurement run to ascertain a specific property. ana = cript.Computation( name="Density analysis", type="analysis", @@ -250,10 +266,33 @@ experiment.computation += [init, equilibration, bulk, ana] New we'd like to upload files associated with our simulation. First, we'll instantiate our File nodes under a specific project. ```python -packing_file = cript.File(name="Initial simulation box snapshot with roughly packed molecules", type="computation_snapshot", source="path/to/local/file") -forcefield_file = cript.File(name="Forcefield definition file", type="data", source="path/to/local/file") -snap_file = cript.File(name="Bulk measurement initial system snap shot", type="computation_snapshot", source="path/to/local/file") -final_file = cript.File(name="Final snapshot of the system at the end the simulations", type="computation_snapshot", source="path/to/local/file") +packing_file = cript.File( + name="Initial simulation box snapshot with roughly packed molecules", + type="computation_snapshot", + source="path/to/local/file", + extension=".csv", +) + +forcefield_file = cript.File( + name="Forcefield definition file", + type="data", + source="path/to/local/file", + extension=".pdf", +) + +snap_file = cript.File( + name="Bulk measurement initial system snap shot", + type="computation_snapshot", + source="path/to/local/file", + extension=".png", +) + +final_file = cript.File( + name="Final snapshot of the system at the end the simulations", + type="computation_snapshot", + source="path/to/local/file", + extension=".jpeg", +) ``` !!! note @@ -309,8 +348,10 @@ Next, we'll link these [`Data`](../../nodes/primary_nodes/data) nodes to the app ```python -# Observe how this step also forms a continuous graph, enabling data to flow from one computation to the next. -# The sequence initiates with the computation process and culminates with the determination of the material property. +# Observe how this step also forms a continuous graph, +# enabling data to flow from one computation to the next. +# The sequence initiates with the computation process +# and culminates with the determination of the material property. init.output_data = [packing_data, forcefield_data] equilibration.input_data = [packing_data, forcefield_data] equilibration.output_data = [equilibration_snap] @@ -373,7 +414,8 @@ Now we can save the project to CRIPT (and upload the files) or inspect the JSON ## Validate CRIPT Project Node ```python # Before we can save it, we should add all the orphaned nodes to the experiments. -# It is important to do this for every experiment separately, but here we only have one. +# It is important to do this for every experiment separately, +# but here we only have one. cript.add_orphaned_nodes_to_project(project, active_experiment=experiment) project.validate() diff --git a/docs/faq.md b/docs/faq.md index 638f0ba5e..8786dd986 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -37,9 +37,10 @@ _We are always looking for ways to improve and create software that is a joy to **Q:** How can I contribute to this project? -**A:** _We would love to have you contribute. -Please read the[GitHub repository wiki](https://github.com/C-Accel-CRIPT/Python-SDK/wiki) -to understand more and get started. Feel free to contribute to any bugs you find, any issues within the +**A:** _We would love to have you contribute! +Please read our [contributing guidelines](https://github.com/C-Accel-CRIPT/Python-SDK/blob/main/CONTRIBUTING.md) +and our [code of conduct](https://github.com/C-Accel-CRIPT/Python-SDK/blob/main/CODE_OF_CONDUCT.md) to get started. +Feel free to contribute to any bugs you find, any issues within the [GitHub repository](https://github.com/C-Accel-CRIPT/Python-SDK/issues), or any features you want._ --- @@ -52,6 +53,13 @@ tab for developer documentation._ --- +**Q:** Is there documentation detailing the internal workings of the code? + +**A:** _Absolutely! For an in-depth look at the CRIPT Python SDK code, +consult the [GitHub repository wiki internal documentation](https://github.com/C-Accel-CRIPT/Python-SDK/wiki)._ + +--- + **Q:** I have this question that is not covered anywhere, where can I ask it? **A:** _Please visit the [CRIPT Python SDK repository](https://github.com/C-Accel-CRIPT/Python-SDK) @@ -74,7 +82,14 @@ A GitHub account is required._ --- -**Q:** Besides the user documentation are there any developer documentation that I can read through on how +**Q:** Where can I find the release notes for each SDK version? + +**A:** _The release notes can be found on our +[CRIPT Python SDK repository releases section](https://github.com/C-Accel-CRIPT/Python-SDK/releases)_ + +--- + +**Q:** Besides the user documentation, is there any developer documentation that I can read through on how the code is written to get a better grasp of it? **A:** _You bet! There are documentation for developers within the diff --git a/docs/nodes/primary_nodes/base_node.md b/docs/nodes/primary_nodes/base_node.md index caf38df1e..232fac480 100644 --- a/docs/nodes/primary_nodes/base_node.md +++ b/docs/nodes/primary_nodes/base_node.md @@ -1 +1 @@ -# Base node +::: cript.nodes.core.BaseNode \ No newline at end of file diff --git a/docs/nodes/primary_nodes/primary_base_node.md b/docs/nodes/primary_nodes/primary_base_node.md new file mode 100644 index 000000000..f6f2dbd8e --- /dev/null +++ b/docs/nodes/primary_nodes/primary_base_node.md @@ -0,0 +1 @@ +::: cript.nodes.primary_nodes.primary_base_node \ No newline at end of file diff --git a/docs/nodes/uuid_base.md b/docs/nodes/uuid_base.md new file mode 100644 index 000000000..f54010f2b --- /dev/null +++ b/docs/nodes/uuid_base.md @@ -0,0 +1 @@ +::: cript.nodes.uuid_base \ No newline at end of file diff --git a/docs/tutorial/cript_installation_guide.md b/docs/tutorial/cript_installation_guide.md index 9608d4f2a..32a595cf9 100644 --- a/docs/tutorial/cript_installation_guide.md +++ b/docs/tutorial/cript_installation_guide.md @@ -7,7 +7,7 @@ ## Steps -1. Install [Python 3.7+](https://www.python.org/downloads/) +1. Install [Python 3.8+](https://www.python.org/downloads/) 2. Create a virtual environment > It is best practice to create a dedicated [python virtual environment](https://docs.python.org/3/library/venv.html) for each python project diff --git a/mkdocs.yml b/mkdocs.yml index b9411547b..31a19f992 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,6 +16,10 @@ nav: - Search Modes: api/search_modes.md - Paginator: api/paginator.md - Controlled Vocabulary Categories: api/controlled_vocabulary_categories.md + - Base Nodes: + - BaseNode: nodes/primary_nodes/base_node.md + - UUIDBase: nodes/uuid_base.md + - PrimaryBaseNode: nodes/primary_nodes/primary_base_node.md - Primary Nodes: - Collection: nodes/primary_nodes/collection.md - Computation: nodes/primary_nodes/computation.md @@ -107,8 +111,8 @@ plugins: show_bases: true show_source: true docstring_style: numpy - watch: - - src/ +watch: + - src/ markdown_extensions: - toc: diff --git a/requirements.txt b/requirements.txt index df746e836..4a8f3e048 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ requests==2.31.0 jsonschema>=4.17.3 -boto3==1.28.39 +boto3>=1.28.39 beartype==0.14.1 diff --git a/requirements_docs.txt b/requirements_docs.txt index 1ea998312..c4a823678 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,5 +1,5 @@ -mkdocs==1.5.2 -mkdocs-material==9.2.6 -mkdocstrings[python]==0.22.0 -pymdown-extensions==10.2.1 -jupytext==1.15.1 +mkdocs==1.5.3 +mkdocs-material==9.4.6 +mkdocstrings[python]==0.23.0 +pymdown-extensions==10.3.1 +jupytext==1.15.2 diff --git a/setup.cfg b/setup.cfg index 83e157762..04e15d0e0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,13 +14,13 @@ classifiers = Topic :: Scientific/Engineering Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 [options] package_dir = =src packages = find: -python_requires = >=3.7 +python_requires = >=3.8 include_package_data = True install_requires = requests==2.31.0 diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 682731721..991a7085d 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -23,6 +23,7 @@ InvalidVocabulary, ) from cript.api.paginator import Paginator +from cript.api.utils.aws_s3_utils import get_s3_client from cript.api.utils.get_host_token import resolve_host_and_token from cript.api.utils.helper_functions import _get_node_type_from_json from cript.api.utils.save_helper import ( @@ -68,7 +69,7 @@ class API: _http_headers: dict = {} _vocabulary: dict = {} _db_schema: dict = {} - _api_handle: str = "api" + _api_prefix: str = "api" _api_version: str = "v1" # trunk-ignore-begin(cspell) @@ -81,6 +82,12 @@ class API: _internal_s3_client: Any = None # type: ignore # trunk-ignore-end(cspell) + # Advanced User Tip: Disabling Node Validation + # For experienced users, deactivating node validation during creation can be a time-saver. + # Note that the complete node graph will still undergo validation before being saved to the back end. + # Caution: It's advisable to keep validation active while debugging scripts, as disabling it can delay error notifications and complicate the debugging process. + skip_validation: bool = False + @beartype def __init__(self, host: Union[str, None] = None, api_token: Union[str, None] = None, storage_token: Union[str, None] = None, config_file_path: Union[str, Path] = ""): """ @@ -93,14 +100,15 @@ def __init__(self, host: Union[str, None] = None, api_token: Union[str, None] = Examples -------- ### Create API client with host and token - ```Python - with cript.API( - host="https://api.criptapp.org/", - api_token="my api token", - storage_token="my storage token", - ) as api: - # node creation, api.save(), etc. - ``` + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... # node creation, api.save(), etc. + ... pass + --- @@ -111,27 +119,25 @@ def __init__(self, host: Union[str, None] = None, api_token: Union[str, None] = as the token might be exposed if the code is shared or stored in a version control system. Anyone that has access to your tokens can impersonate you on the CRIPT platform - ### Create API Client with - [Environment Variables](https://www.freecodecamp.org/news/python-env-vars-how-to-get-an-environment-variable-in-python/) + ### Create API Client with Environment Variables + Another great way to keep sensitive information secure is by using [environment variables](https://www.freecodecamp.org/news/python-env-vars-how-to-get-an-environment-variable-in-python/). Sensitive information can be securely stored in environment variables and loaded into the code using [os.getenv()](https://docs.python.org/3/library/os.html#os.getenv). - #### Example - - ```python - import os - - # securely load sensitive data into the script - cript_host = os.getenv("cript_host") - cript_api_token = os.getenv("cript_api_token") - cript_storage_token = os.getenv("cript_storage_token") - - with cript.API(host=cript_host, api_token=cript_api_token, storage_token=cript_storage_token) as api: - # write your script - pass - ``` + Examples + -------- + >>> import cript + >>> import os + >>> # securely load sensitive data into the script + >>> cript_host = os.getenv("cript_host") + >>> cript_api_token = os.getenv("cript_api_token") + >>> cript_storage_token = os.getenv("cript_storage_token") + >>> with cript.API( + ... host=cript_host, api_token=cript_api_token, storage_token=cript_storage_token + ... ) as api: + ... pass ### Create API Client with None Alternatively you can configure your system to have an environment variable of @@ -156,16 +162,16 @@ def __init__(self, host: Union[str, None] = None, api_token: Union[str, None] = } ``` + Examples + -------- `my_script.py` - ```python - from pathlib import Path - - # create a file path object of where the config file is - config_file_path = Path(__file__) / Path('./config.json') - - with cript.API(config_file_path=config_file_path) as api: - # node creation, api.save(), etc. - ``` + >>> from pathlib import Path + >>> import cript + >>> # create a file path object of where the config file is + >>> config_file_path = Path(__file__) / Path('./config.json') + >>> with cript.API(config_file_path=config_file_path) as api: # doctest: +SKIP + ... # node creation, api.save(), etc. + ... pass Parameters ---------- @@ -234,6 +240,17 @@ def __str__(self) -> str: """ States the host of the CRIPT API client + Examples + -------- + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... print(api) + CRIPT API Client - Host URL: 'https://api.criptapp.org/api/v1' + Returns ------- str @@ -298,10 +315,14 @@ def verbose(self) -> bool: Examples -------- - ```python - # turn off the terminal logs - api.verbose = False - ``` + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... # turn off the terminal logs + ... api.verbose = False Returns ------- @@ -329,9 +350,34 @@ def verbose(self, new_verbose_value: bool) -> None: @beartype def _prepare_host(self, host: str) -> str: + """ + Takes the host URL provided by the user during API object construction (e.g., `https://api.criptapp.org`) + and standardizes it for internal use. Performs any required string manipulation to ensure uniformity. + + Parameters + ---------- + host: str + The host URL specified during API initialization, typically in the form `https://api.criptapp.org`. + + Warnings + -------- + If the specified host uses the unsafe "http://" protocol, a warning will be raised to consider using HTTPS. + + Raises + ------ + InvalidHostError + If the host string does not start with either "http" or "https", an InvalidHostError will be raised. + Only HTTP protocol is acceptable at this time. + + Returns + ------- + str + A standardized host string formatted for internal use. + + """ # strip ending slash to make host always uniform host = host.rstrip("/") - host = f"{host}/{self._api_handle}/{self._api_version}" + host = f"{host}/{self._api_prefix}/{self._api_version}" # if host is using unsafe "http://" then give a warning if host.startswith("http://"): @@ -346,29 +392,19 @@ def _prepare_host(self, host: str) -> str: @property def _s3_client(self) -> boto3.client: # type: ignore """ - creates or returns a fully authenticated and ready s3 client + Property to use when wanting to interact with AWS S3. + + Gets a fully authenticated AWS S3 client if it was never created and stash it, + if the AWS S3 client has been created before, then returns the client that it has Returns ------- s3_client: boto3.client fully prepared and authenticated s3 client ready to be used throughout the script """ - if self._internal_s3_client is None: - auth = boto3.client("cognito-identity", region_name=self._REGION_NAME) - identity_id = auth.get_id(IdentityPoolId=self._IDENTITY_POOL_ID, Logins={self._COGNITO_LOGIN_PROVIDER: self._storage_token}) - # TODO remove this temporary fix to the token, by getting is from back end. - aws_token = self._storage_token - - aws_credentials = auth.get_credentials_for_identity(IdentityId=identity_id["IdentityId"], Logins={self._COGNITO_LOGIN_PROVIDER: aws_token}) - aws_credentials = aws_credentials["Credentials"] - s3_client = boto3.client( - "s3", - aws_access_key_id=aws_credentials["AccessKeyId"], - aws_secret_access_key=aws_credentials["SecretKey"], - aws_session_token=aws_credentials["SessionToken"], - ) - self._internal_s3_client = s3_client + self._internal_s3_client = get_s3_client(region_name=self._REGION_NAME, identity_pool_id=self._IDENTITY_POOL_ID, cognito_login_provider=self._COGNITO_LOGIN_PROVIDER, storage_token=self._storage_token) + return self._internal_s3_client def __enter__(self): @@ -424,21 +460,22 @@ def host(self): The term "host" designates the specific CRIPT instance to which you intend to upload your data. - For most users, the host will be `api.criptapp.org` + For most users, the host will be `https://api.criptapp.org` ```yaml - host: api.criptapp.org + host: https://api.criptapp.org ``` Examples -------- - ```python - print(cript_api.host) - ``` - Output - ```Python + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... print(api.host) https://api.criptapp.org/api/v1 - ``` """ return self._host @@ -499,6 +536,17 @@ def get_vocab_by_category(self, category: VocabCategories) -> List[dict]: """ get the CRIPT controlled vocabulary by category + Examples + -------- + >>> import os + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... api.get_vocab_by_category(cript.VocabCategories.MATERIAL_IDENTIFIER_KEY) # doctest: +SKIP + Parameters ---------- category: str @@ -520,7 +568,6 @@ def get_vocab_by_category(self, category: VocabCategories) -> List[dict]: response: Dict = requests.get(url=vocabulary_category_url, timeout=_API_TIMEOUT).json() if response["code"] != 200: - # TODO give a better CRIPT custom Exception raise APIError(api_error=str(response), http_method="GET", api_url=vocabulary_category_url) # add to cache @@ -609,7 +656,7 @@ def _get_db_schema(self) -> dict: return self._db_schema @beartype - def _is_node_schema_valid(self, node_json: str, is_patch: bool = False) -> bool: + def _is_node_schema_valid(self, node_json: str, is_patch: bool = False, force_validation: bool = False) -> Union[bool, None]: """ checks a node JSON schema against the db schema to return if it is valid or not. @@ -643,6 +690,10 @@ def _is_node_schema_valid(self, node_json: str, is_patch: bool = False) -> bool: whether the node JSON is valid or not """ + # Fast exit without validation + if self.skip_validation and not force_validation: + return None + db_schema = self._get_db_schema() node_type: str = _get_node_type_from_json(node_json=node_json) @@ -651,7 +702,13 @@ def _is_node_schema_valid(self, node_json: str, is_patch: bool = False) -> bool: # logging out info to the terminal for the user feedback # (improve UX because the program is currently slow) - self.logger.info(f"Validating {node_type} graph...") + log_message = f"Validating {node_type} graph..." + if force_validation: + log_message = "Forced: " + log_message + " if error occur, try setting `cript.API.skip_validation = False` for debugging." + else: + log_message += " (Can be disabled by setting `cript.API.skip_validation = True`.)" + + self.logger.info(log_message) # set the schema to test against http POST or PATCH of DB Schema schema_http_method: str @@ -721,7 +778,7 @@ def _internal_save(self, node, save_values: Optional[_InternalSaveValues] = None for file_node in node.find_children({"node": ["File"]}): file_node.ensure_uploaded(api=self) - node.validate() + node.validate(force_validation=True) # Dummy response to have a virtual do-while loop, instead of while loop. response = {"code": -1} @@ -823,16 +880,29 @@ def upload_file(self, file_path: Union[Path, str]) -> str: Examples -------- - ```python - import cript + >>> from pathlib import Path + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... # programmatically create the absolute path of your file, so the program always works correctly + ... my_file_path = (Path(__file__) / Path('../upload_files/my_file.txt')).resolve() + ... my_file_cloud_storage_source = api.upload_file(file_path=my_file_path) # doctest: +SKIP - api = cript.API(host, token) + Notes + ----- + We recommend using a [Path](https://docs.python.org/3/library/pathlib.html) object for specifying a file path. + Using the Python [pathlib library](https://docs.python.org/3/library/pathlib.html) provides platform-agnostic approach + for filesystem operations, ensuring seamless functionality across different operating systems. + Additionally, [Path](https://docs.python.org/3/library/pathlib.html) objects offer various built-in methods + for more sophisticated and secure file handling and has a easy to use interface that can make working with it a breeze + and can help reduce errors. - # programmatically create the absolute path of your file, so the program always works correctly - my_file_path = (Path(__file__) / Path('../upload_files/my_file.txt')).resolve() + Other options include using a raw string for relative/absolute file path, + or using the [os.path module](https://docs.python.org/3/library/os.path.html). - my_file_s3_url = api.upload_file(absolute_file_path=my_file_path) - ``` Raises ------ @@ -868,7 +938,7 @@ def upload_file(self, file_path: Union[Path, str]) -> str: # upload file to AWS S3 self._s3_client.upload_file(Filename=file_path, Bucket=self._BUCKET_NAME, Key=object_name) # type: ignore - self.logger.info(f"Uploaded file: '{file_path}' to CRIPT storage") + self.logger.info(f"Uploaded File: '{file_path}' to CRIPT storage") # return the object_name within AWS S3 for easy retrieval return object_name @@ -917,12 +987,21 @@ def download_file(self, file_source: str, destination_path: str = ".") -> None: Examples -------- - ```python - from pathlib import Path - - desktop_path = (Path(__file__).parent / "cript_downloads" / "my_downloaded_file.txt").resolve() - cript_api.download_file(file_url=my_file_source, destination_path=desktop_path) - ``` + >>> from pathlib import Path + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... desktop_path = (Path(__file__).parent / "cript_downloads" / "my_downloaded_file.txt").resolve() + ... my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... ) + ... api.download_file(file_source=my_file.source, destination_path=str(desktop_path)) # doctest: +SKIP Raises ------ @@ -946,7 +1025,7 @@ def download_file(self, file_source: str, destination_path: str = ".") -> None: @beartype def search( self, - node_type, + node_type: Any, search_mode: SearchModes, value_to_search: Optional[str], ) -> Paginator: @@ -1024,7 +1103,8 @@ def search( To learn more about working with pagination, please refer to our [paginator object documentation](../paginator). - Additionally, you can utilize the utility function [`load_nodes_from_json(node_json)`](../../utility_functions/#cript.nodes.util.load_nodes_from_json) + Additionally, you can utilize the utility function + [`load_nodes_from_json(node_json)`](../../utility_functions/#cript.nodes.util.load_nodes_from_json) to convert API JSON responses into Python SDK nodes. ???+ Example "Convert API JSON Response to Python SDK Nodes" @@ -1085,18 +1165,26 @@ def delete(self, node) -> None: Examples -------- - ```python - api.delete(node=my_material_node) - ``` + >>> import cript + >>> my_material_node = cript.Material( + ... name="my component material 1", + ... identifier=[{"amino_acid": "component 1 alternative name"}], + ... ) + >>> api.delete(node=my_material_node) # doctest: +SKIP Notes ----- After the node has been successfully deleted, a log is written to the terminal if `cript.API.verbose = True` ```bash - INFO: Deleted 'Data' with UUID of '80bfc642-157e-4692-a547-97c470725397' from CRIPT API. + INFO: Deleted 'Material' with UUID of '80bfc642-157e-4692-a547-97c470725397' from CRIPT API. ``` + ??? info "Implementation Details" + Under the hood, this method actually calls + [delete_node_by_uuid](./#cript.api.api.API.delete_node_by_uuid) + with the node_type and node UUID + Warnings -------- After successfully deleting a node from the API, keep in mind that your local Project node in your script @@ -1125,12 +1213,89 @@ def delete(self, node) -> None: ------- None """ + self.delete_node_by_uuid(node_type=node.node_type_snake_case, node_uuid=str(node.uuid)) + + @beartype + def delete_node_by_uuid(self, node_type: str, node_uuid: str) -> None: + """ + Simply deletes the desired node from the CRIPT API and writes a log in the terminal that the node has been + successfully deleted. + + Examples + -------- + >>> import cript + >>> with cript.API( + ... host="https://api.criptapp.org/", + ... api_token=os.getenv("CRIPT_TOKEN"), + ... storage_token=os.getenv("CRIPT_STORAGE_TOKEN") + ... ) as api: + ... api.delete_node_by_uuid( + ... node_type="computation_process", + ... node_uuid="2fd3d500-304d-4a06-8628-a79b59344b2f" + ... ) # doctest: +SKIP + + ??? "How to get `node_type in snake case`" + You can get the `node type in snake case` of a node via: + ```python + import cript + print(cript.ComputationProcess.node_type_snake_case) + computation_process + ``` + + You can also call `api.delete_node_by_uuid()` with + ```python + api.delete( + node_type=cript.ComputationProcess.node_type_snake_case, + node_uuid="2fd3d500-304d-4a06-8628-a79b59344b2f", + ) + ``` - delete_node_api_url: str = f"{self._host}/{node.node_type_snake_case}/{node.uuid}/" + Notes + ----- + After the node has been successfully deleted, a log is written to the terminal if `cript.API.verbose = True` + + ```bash + INFO: Deleted 'Material' with UUID of '80bfc642-157e-4692-a547-97c470725397' from CRIPT API. + ``` + + Warnings + -------- + After successfully deleting a node from the API, keep in mind that your local Project node in your script + may still contain outdated data as it has not been synced with the API. + + To ensure you have the latest data, follow these steps: + + 1. Fetch the newest Project node from the API using the + [`cript.API.search()`](./#cript.api.api.API.search) provided by the SDK. + 1. Deserialize the retrieved data into a new Project node using the + [`load_nodes_from_json`](../../utility_functions/#cript.nodes.util.load_nodes_from_json) utility function. + 1. Replace your old Project node with the new one in your script for accurate and up-to-date information. + + Parameters + ---------- + node_type: str + the type of node that you want to delete in snake case + node_uuid: str + the UUID of the primary node, supporting node, or sub-object + that you want to delete from the API + + Raises + ------ + APIError + If the API responds with anything other than HTTP status 200, then the CRIPT Python SDK raises `APIError` + `APIError` is raised in case the API cannot delete the specified node. + Such cases can happen if you do not have permission to delete the node + or if the node is actively being used elsewhere in CRIPT platform and the API cannot delete it. + + Returns + ------- + None + """ + delete_node_api_url: str = f"{self._host}/{node_type.lower()}/{node_uuid}/" response: Dict = requests.delete(headers=self._http_headers, url=delete_node_api_url, timeout=_API_TIMEOUT).json() if response["code"] != 200: raise APIError(api_error=str(response), http_method="DELETE", api_url=delete_node_api_url) - self.logger.info(f"Deleted '{node.node_type}' with UUID of '{node.uuid}' from CRIPT API.") + self.logger.info(f"Deleted '{node_type.title()}' with UUID of '{node_uuid}' from CRIPT API.") diff --git a/src/cript/api/exceptions.py b/src/cript/api/exceptions.py index 3332a753b..b303903a9 100644 --- a/src/cript/api/exceptions.py +++ b/src/cript/api/exceptions.py @@ -1,5 +1,7 @@ import json -from typing import List, Optional, Set +from typing import List, Set + +from beartype import beartype from cript.exceptions import CRIPTException @@ -9,7 +11,7 @@ class CRIPTConnectionError(CRIPTException): ## Definition Raised when the cript.API object cannot connect to CRIPT with the given host and token - ## How to Fix + ## Troubleshooting The best way to fix this error is to check that your host and token are written and used correctly within the cript.API object. This error could also be shown if the API is unresponsive and the cript.API object just cannot successfully connect to it. @@ -29,7 +31,6 @@ def __str__(self) -> str: return error_message -# TODO refactor class InvalidVocabulary(CRIPTException): """ Raised when the CRIPT controlled vocabulary is invalid @@ -72,7 +73,7 @@ class CRIPTAPIRequiredError(CRIPTException): The cript.API object may be explicitly called by the user to perform operations to the API, or implicitly called by the Python SDK under the hood to perform some sort of validation. - ## How to Fix + ## Troubleshooting To fix this error please instantiate an api object ```python @@ -100,7 +101,7 @@ class CRIPTAPISaveError(CRIPTException): CRIPTAPISaveError is raised when the API responds with a http status code that is anything other than 200. The status code and API response is shown to the user to help them debug the issue. - ## How to Fix + ## Troubleshooting This error is more of a case by case basis, but the best way to approach it to understand that the CRIPT Python SDK sent an HTTP POST request with a giant JSON in the request body to the CRIPT API. The API then read that request, and it responded with some sort of error either @@ -131,6 +132,27 @@ def __str__(self) -> str: class CRIPTDuplicateNameError(CRIPTAPISaveError): + """ + Exception raised when attempting to save a node with a name that already exists in CRIPT. + + This exception class extends `CRIPTAPISaveError` and is used to handle errors + that occur when a node's name duplicates an existing node's name in the CRIPT database. + + Parameters + ---------- + api_response : dict + The response returned from the API that contains the error details. + json_data : str + The JSON data of the node that caused the duplication error. + parent_cript_save_error : CRIPTAPISaveError + The original `CRIPTAPISaveError` instance containing additional context of the error. + + ## Troubleshooting + #### Duplicate Name Errors + Make sure that the name you are using to save a node is unique and does not already exist in the database. + If you encounter a `CRIPTDuplicateNameError`, check the name of your node and try a different name. + """ + def __init__(self, api_response, json_data: str, parent_cript_save_error: CRIPTAPISaveError): super().__init__( parent_cript_save_error.api_host_domain, api_response["code"], api_response=api_response["error"], patch_request=parent_cript_save_error.patch_request, pre_saved_nodes=parent_cript_save_error.pre_saved_nodes, json_data=json_data @@ -156,7 +178,7 @@ def __init__(self, api_response, json_data: str, parent_cript_save_error: CRIPTA self.node = "UnknownTypeIdx" def __str__(self) -> str: - return f"The name '{self.name}' for your {self.node} node is already present in CRIPT. Either choose a new name!" # , or consider namespaces like 'MyNameSpace::{self.name}' instead." + return f"The name '{self.name}' for your {self.node} node is already present in CRIPT. Please use a unique name" class InvalidHostError(CRIPTException): @@ -164,7 +186,7 @@ class InvalidHostError(CRIPTException): ## Definition Exception is raised when the host given to the API is invalid - ## How to Fix + ## Troubleshooting This is a simple error to fix, simply put `http://` or preferably `https://` in front of your domain when passing in the host to the cript.API class such as `https://api.criptapp.org/` @@ -201,24 +223,32 @@ class APIError(CRIPTException): ## Definition This is a generic error made to display API errors to the user to troubleshoot. - ## How to Fix + ## Troubleshooting Please keep in mind that the CRIPT Python SDK turns the [Project](../../nodes/primary_nodes/project) node into a giant JSON and sends that to the API to be processed. If there are any errors while processing the giant JSON generated by the CRIPT Python SDK, then the API will return an error about the http request - and the JSON sent to it. Therefore, the error shown might be an error within the JSON and not particular - within the Python code that was created + and the JSON sent to it. The best way to trouble shoot this is to figure out what the API error means and figure out where - in the Python SDK this error occurred and what have been the reason under the hood. + in the Python SDK this error occurred and what could be the reason under the hood. + + ### Steps to try: + 1. What does the API error mean? + 1. Is the error something you can easily fix by maybe renaming a node? + 1. Does the `http_method` look reasonable? + 1. Does the `URL` that the data was sent to look reasonable? + 1. Is there a problem within the JSON? + 1. Is the problem within how the SDK is converting nodes from and to JSON (serialization and deserialization)? """ api_error: str = "" # having the URL that the API gave an error for helps in debugging - api_url: Optional[str] = None - http_method: Optional[str] = None + api_url: str = "" + http_method: str = "" - def __init__(self, api_error: str, http_method: Optional[str] = None, api_url: Optional[str] = None) -> None: + @beartype + def __init__(self, api_error: str, http_method: str, api_url: str) -> None: self.api_error = api_error self.api_url = api_url @@ -228,14 +258,7 @@ def __init__(self, api_error: str, http_method: Optional[str] = None, api_url: O self.http_method = http_method def __str__(self) -> str: - # TODO refactor all of SDK using this error to be used the same to avoid this logic and Optional attributes - # this logic currently exists to make previous SDK work - - # if you have the URL then display it, otherwise just show the error message - if self.api_url and self.http_method: - return f"CRIPT Python SDK sent HTTP `{self.http_method.upper()}` request to URL: `{self.api_url}` and API responded with {self.api_error}" - else: - return f"The API responded with: {self.api_error}" + return f"CRIPT Python SDK sent HTTP `{self.http_method.upper()}` request to URL: `{self.api_url}` and API responded with {self.api_error}" class FileDownloadError(CRIPTException): diff --git a/src/cript/api/paginator.py b/src/cript/api/paginator.py index 8711e2d0a..f829a0d27 100644 --- a/src/cript/api/paginator.py +++ b/src/cript/api/paginator.py @@ -163,8 +163,7 @@ def current_page_number(self, new_page_number: int) -> None: if new_page_number < 0: error_message: str = f"Paginator current page number is invalid because it is negative: " f"{self.current_page_number} please set paginator.current_page_number " f"to a positive page number" - # TODO replace with custom error - raise Exception(error_message) + raise RuntimeError(error_message) else: self._current_page_number = new_page_number @@ -226,7 +225,8 @@ def fetch_page_from_api(self) -> List[dict]: self.current_page_results = [] return self.current_page_results + # if API response is not 200 raise error for the user to debug if api_response["code"] != 200: - raise APIError(api_error=str(response), http_method="GET", api_url=temp_api_endpoint) + raise APIError(api_error=str(response.json()), http_method="GET", api_url=temp_api_endpoint) return self.current_page_results diff --git a/src/cript/api/utils/aws_s3_utils.py b/src/cript/api/utils/aws_s3_utils.py new file mode 100644 index 000000000..a3d507d19 --- /dev/null +++ b/src/cript/api/utils/aws_s3_utils.py @@ -0,0 +1,40 @@ +from typing import Any + +import boto3 +from beartype import beartype + + +@beartype +def get_s3_client(region_name: str, identity_pool_id: str, cognito_login_provider: str, storage_token: str) -> Any: + """ + Creates an AWS S3 client and returns it to be used in the `cript.API` class. + + Parameters + ---------- + region_name: str + AWS S3 region name + identity_pool_id: str + AWS S3 identity pool id + cognito_login_provider: str + AWS S3 cognito login provider + storage_token: str + AWS S3 storage token gotten from the CRIPT frontend + + Returns + ------- + boto3.client + fully working AWS S3 client + """ + auth = boto3.client("cognito-identity", region_name=region_name) + identity_id = auth.get_id(IdentityPoolId=identity_pool_id, Logins={cognito_login_provider: storage_token}) + aws_token = storage_token + + aws_credentials = auth.get_credentials_for_identity(IdentityId=identity_id["IdentityId"], Logins={cognito_login_provider: aws_token}) + aws_credentials = aws_credentials["Credentials"] + s3_client = boto3.client( + "s3", + aws_access_key_id=aws_credentials["AccessKeyId"], + aws_secret_access_key=aws_credentials["SecretKey"], + aws_session_token=aws_credentials["SessionToken"], + ) + return s3_client diff --git a/src/cript/api/valid_search_modes.py b/src/cript/api/valid_search_modes.py index 8805fc2a0..206f4b540 100644 --- a/src/cript/api/valid_search_modes.py +++ b/src/cript/api/valid_search_modes.py @@ -20,14 +20,13 @@ class SearchModes(Enum): Examples ------- - ```python - # search by node type - materials_paginator = cript_api.search( - node_type=cript.Material, - search_mode=cript.SearchModes.NODE_TYPE, - value_to_search=None, - ) - ``` + >>> import cript + >>> # search by node type + >>> materials_paginator = api.search( + ... node_type=cript.Material, + ... search_mode=cript.SearchModes.NODE_TYPE, + ... value_to_search=None, + ... ) # doctest: +SKIP For more details and code examples, please check the [cript.API.search( ) method](../api/#cript.api.api.API.search) diff --git a/src/cript/api/vocabulary_categories.py b/src/cript/api/vocabulary_categories.py index c1969236e..1107f3e00 100644 --- a/src/cript/api/vocabulary_categories.py +++ b/src/cript/api/vocabulary_categories.py @@ -64,11 +64,10 @@ class VocabCategories(Enum): Examples -------- - ```python - algorithm_vocabulary = api.get_vocabulary_by_category( - cript.VocabCategories.ALGORITHM_KEY - ) - ``` + >>> import cript + >>> algorithm_vocabulary = api.get_vocab_by_category( + ... cript.VocabCategories.ALGORITHM_KEY + ... ) """ ALGORITHM_KEY: str = "algorithm_key" diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 315921944..f383ce0bb 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -142,7 +142,7 @@ def _update_json_attrs_if_valid(self, new_json_attr: JsonAttributes) -> None: self._json_attrs = old_json_attrs raise exc - def validate(self, api=None, is_patch=False) -> None: + def validate(self, api=None, is_patch: bool = False, force_validation: bool = False) -> None: """ Validate this node (and all its children) against the schema provided by the data bank. @@ -154,7 +154,7 @@ def validate(self, api=None, is_patch=False) -> None: if api is None: api = _get_global_cached_api() - api._is_node_schema_valid(self.get_json(is_patch=is_patch).json, is_patch=is_patch) + api._is_node_schema_valid(self.get_json(is_patch=is_patch).json, is_patch=is_patch, force_validation=force_validation) @classmethod def _from_json(cls, json_dict: dict): @@ -239,8 +239,172 @@ def json(self): """ # We cannot validate in `get_json` because we call it inside `validate`. # But most uses are probably the property, so we can validate the node here. - self.validate() - return self.get_json().json + json_string: str = self.get_json().json + + from cript.api.api import _get_global_cached_api + + api = _get_global_cached_api() + api._is_node_schema_valid(json_string, force_validation=True) + + return json_string + + def get_expanded_json(self, **kwargs) -> str: + """ + Generates a long-form JSON representation of the current node and its hierarchy. + + The long-form JSON includes complete details of the node, eliminating the need for + references to UUIDs to nodes stored in the CRIPT database. This comprehensive representation + is useful for offline storage of CRIPT nodes, transferring nodes between different CRIPT instances, + or for backup purposes. + + The generated long-form JSON can be reloaded into the SDK using + [`cript.load_nodes_from_json()`](../../../utility_functions/#cript.nodes.util.load_nodes_from_json), + ensuring consistency and completeness of the node data. + However, it's important to note that this long-form JSON might not comply directly with the JSON schema + required for POST or PATCH requests to the CRIPT API. + + Optional keyword arguments (`kwargs`) are supported and are passed directly to `json.dumps()`. + These arguments allow customization of the JSON output, such as formatting for readability + or pretty printing. + + Parameters + ---------- + **kwargs : dict, optional + Additional keyword arguments for `json.dumps()` to customize the JSON output, such as `indent` + for pretty-printing. + + Returns + ------- + str + A comprehensive JSON string representing the current node and its entire hierarchy in long-form. + + Notes + ----- + The `get_expanded_json()` method differs from the standard [`json`](./#cript.nodes.core.BaseNode.json) + property or method, which might provide a more condensed version of the node's data. + + > For more information on condensed JSON and deserialization, please feel free to reference our discussion + > on [deserializing Python nodes to JSON](https://github.com/C-Accel-CRIPT/Python-SDK/discussions/177) + + Examples + -------- + >>> import cript + >>> # ============= Create all needed nodes ============= + >>> my_project = cript.Project(name=f"my_Project") + >>> my_collection = cript.Collection(name="my collection") + >>> my_material_1 = cript.Material( + ... name="my material 1", identifier=[{"bigsmiles": "my material 1 bigsmiles"}] + ... ) + >>> my_material_2 = cript.Material( + ... name="my material 2", identifier=[{"bigsmiles": "my material 2 bigsmiles"}] + ... ) + >>> my_inventory = cript.Inventory( + ... name="my inventory", material=[my_material_1, my_material_2] + ... ) + >>> # ============= Assemble nodes ============= + >>> my_project.collection = [my_collection] + >>> my_project.collection[0].inventory = [my_inventory] + >>> # ============= Get long form JSON ============= + >>> long_form_json = my_project.get_expanded_json(indent=4) + + ???+ info "Condensed JSON VS Expanded JSON" + # Default Condensed JSON + > This is the JSON when `my_project.json` is called + + ```json linenums="1" + { + "node":[ + "Project" + ], + "uid":"_:d0d1b3c9-d552-4d4f-afd2-76f01538b87a", + "uuid":"d0d1b3c9-d552-4d4f-afd2-76f01538b87a", + "name":"my_Project", + "collection":[ + { + "node":[ + "Collection" + ], + "uid":"_:07765ac8-862a-459e-9d99-d0439d6a6a09", + "uuid":"07765ac8-862a-459e-9d99-d0439d6a6a09", + "name":"my collection", + "inventory":[ + { + "node":[ + "Inventory" + ], + "uid":"_:4cf2bbee-3dc0-400b-8269-709f99d89d9f", + "uuid":"4cf2bbee-3dc0-400b-8269-709f99d89d9f", + "name":"my inventory", + "material":[ + { + "uuid":"0cf14572-4da2-43f2-8cb9-e8374086368e" + }, + { + "uuid":"6302a8b0-4265-4a3a-a40f-bbcbb7293046" + } + ] + } + ] + } + ] + } + ``` + + # Expanded JSON + > This is what is created when `my_project.get_expanded_json()` + + ```json linenums="1" + { + "node":[ + "Project" + ], + "uid":"_:afe4bb2f-fa75-4736-b692-418a5143e6f5", + "uuid":"afe4bb2f-fa75-4736-b692-418a5143e6f5", + "name":"my_Project", + "collection":[ + { + "node":[ + "Collection" + ], + "uid":"_:8b5c8125-c956-472a-9d07-8cb7b402b101", + "uuid":"8b5c8125-c956-472a-9d07-8cb7b402b101", + "name":"my collection", + "inventory":[ + { + "node":[ + "Inventory" + ], + "uid":"_:1bd3c966-cb35-494d-85cd-3515cde570f3", + "uuid":"1bd3c966-cb35-494d-85cd-3515cde570f3", + "name":"my inventory", + "material":[ + { + "node":[ + "Material" + ], + "uid":"_:07bc3e4f-757f-4ac7-ae8a-7a0c68272531", + "uuid":"07bc3e4f-757f-4ac7-ae8a-7a0c68272531", + "name":"my material 1", + "bigsmiles":"my material 1 bigsmiles" + }, + { + "node":[ + "Material" + ], + "uid":"_:64565687-5707-4d67-860f-5ee4a057a45f", + "uuid":"64565687-5707-4d67-860f-5ee4a057a45f", + "name":"my material 2", + "bigsmiles":"my material 2 bigsmiles" + } + ] + } + ] + } + ] + } + ``` + """ + return self.get_json(handled_ids=None, known_uuid=None, suppress_attributes=None, is_patch=False, condense_to_uuid={}, **kwargs).json def get_json( self, @@ -266,6 +430,8 @@ def get_json( User facing access to get the JSON of a node. Opposed to the also available property json this functions allows further control. Additionally, this function does not call `self.validate()` but the property `json` does. + We also accept `kwargs`, that are passed on to the JSON decoding via `json.dumps()` this can be used for example to prettify the output. + Returns named tuple with json and handled ids as result. """ diff --git a/src/cript/nodes/exceptions.py b/src/cript/nodes/exceptions.py index cdf082fa1..bfb427901 100644 --- a/src/cript/nodes/exceptions.py +++ b/src/cript/nodes/exceptions.py @@ -29,7 +29,7 @@ class CRIPTNodeSchemaError(CRIPTException): * The format of the JSON the CRIPT Python SDK created was invalid 1. There is something wrong with the database schema - ## How to Fix + ## Troubleshooting The easiest way to troubleshoot this is to examine the JSON that the SDK created via printing out the [Project](../../nodes/primary_nodes/project) node's JSON and checking the place that the schema validation says failed @@ -74,7 +74,7 @@ class CRIPTJsonDeserializationError(CRIPTException): ```json ``` - ## How to Fix + ## Troubleshooting """ def __init__(self, node_type: str, json_str: str) -> None: @@ -109,16 +109,21 @@ class CRIPTDeserializationUIDError(CRIPTException): ```json { - "node": ["Algorithm"], - "key": "mc_barostat", - "type": "barostat", - "parameter": {"node": ["Parameter"], "uid": "uid-string", - "key": "update_frequency", "value":1, "unit": "1/second"} + "node":["Algorithm"], + "key":"mc_barostat", + "type":"barostat", + "parameter":{ + "node":["Parameter"], + "uid":"uid-string", + "key":"update_frequency", + "value":1, + "unit":"1/second" + } } ``` Now the node is fully specified. - ## How to Fix + ## Troubleshooting Specify the full node instead. This error might appear if you try to partially load previously generated JSON. """ @@ -174,7 +179,7 @@ class CRIPTJsonNodeError(CRIPTJsonDeserializationError): ``` - ## How to Fix + ## Troubleshooting Debugging skills are most helpful here as there is no one-size-fits-all approach. It is best to identify whether the invalid JSON was created in the Python SDK @@ -207,7 +212,7 @@ class CRIPTJsonSerializationError(CRIPTException): ## Definition This Exception is raised if serialization of node from JSON to Python Object fails. - ## How to Fix + ## Troubleshooting """ def __init__(self, node_type: str, json_dict: str) -> None: @@ -259,7 +264,7 @@ class CRIPTOrphanedNodesError(CRIPTException, ABC): If there is a material node that is used within a project but not a part of the inventory and the validation code finds it then it raises an `CRIPTOrphanedNodeError` - ## How To Fix + ## Troubleshooting Fixing this is simple and easy, just take the node that CRIPT Python SDK found a problem with and associate it with the appropriate parent via @@ -281,7 +286,7 @@ class CRIPTOrphanedMaterialError(CRIPTOrphanedNodesError): ## Definition CRIPTOrphanedNodesError, but specific for orphaned materials. - ## How To Fix + ## Troubleshooting Handle this error by adding the orphaned materials into the parent project or its inventories. """ @@ -301,7 +306,7 @@ class CRIPTOrphanedExperimentError(CRIPTOrphanedNodesError): ## Definition CRIPTOrphanedNodesError, but specific for orphaned nodes that should be listed in one of the experiments. - ## How To Fix + ## Troubleshooting Handle this error by adding the orphaned node into one the parent project's experiments. """ @@ -343,7 +348,7 @@ class CRIPTOrphanedDataError(CRIPTOrphanedExperimentError): ## Definition CRIPTOrphanedExperimentError, but specific for orphaned Data node that should be listed in one of the experiments. - ## How To Fix + ## Troubleshooting Handle this error by adding the orphaned node into one the parent project's experiments `data` attribute. """ @@ -357,7 +362,7 @@ class CRIPTOrphanedProcessError(CRIPTOrphanedExperimentError): CRIPTOrphanedExperimentError, but specific for orphaned Process node that should be listed in one of the experiments. - ## How To Fix + ## Troubleshooting Handle this error by adding the orphaned node into one the parent project's experiments `process` attribute. """ @@ -372,7 +377,7 @@ class CRIPTOrphanedComputationError(CRIPTOrphanedExperimentError): CRIPTOrphanedExperimentError, but specific for orphaned Computation node that should be listed in one of the experiments. - ## How To Fix + ## Troubleshooting Handle this error by adding the orphaned node into one the parent project's experiments `Computation` attribute. """ @@ -387,7 +392,7 @@ class CRIPTOrphanedComputationalProcessError(CRIPTOrphanedExperimentError): CRIPTOrphanedExperimentError, but specific for orphaned ComputationalProcess node that should be listed in one of the experiments. - ## How To Fix + ## Troubleshooting Handle this error by adding the orphaned node into one the parent project's experiments `ComputationalProcess` attribute. """ diff --git a/src/cript/nodes/primary_nodes/collection.py b/src/cript/nodes/primary_nodes/collection.py index 7fd29e13a..b7763d739 100644 --- a/src/cript/nodes/primary_nodes/collection.py +++ b/src/cript/nodes/primary_nodes/collection.py @@ -47,8 +47,6 @@ class Collection(PrimaryBaseNode): "citation":[], } ``` - - """ @dataclass(frozen=True) @@ -73,6 +71,11 @@ def __init__(self, name: str, experiment: Optional[List[Any]] = None, inventory: create a Collection with a name add list of experiment, inventory, citation, doi, and notes if available. + Examples + -------- + >>> import cript + >>> my_collection = cript.Collection(name="my collection name") + Parameters ---------- name: str @@ -131,9 +134,10 @@ def experiment(self) -> List[Any]: Examples -------- - ```python - my_collection.experiment = [my_first_experiment] - ``` + >>> import cript + >>> my_collection = cript.Collection(name="my collection name") + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_collection.experiment = [my_experiment] Returns ------- @@ -168,23 +172,20 @@ def inventory(self) -> List[Any]: Examples -------- - ```python - material_1 = cript.Material( - name="material 1", - identifier=[{"alternative_names": "material 1 alternative name"}], - ) - - material_2 = cript.Material( - name="material 2", - identifier=[{"alternative_names": "material 2 alternative name"}], - ) - - my_inventory = cript.Inventory( - name="my inventory name", materials_list=[material_1, material_2] - ) - - my_collection.inventory = [my_inventory] - ``` + >>> import cript + >>> my_collection = cript.Collection(name="my collection name") + >>> material_1 = cript.Material( + ... name="material 1", + ... identifier=[{"bigsmiles": "material 1 bigsmiles"}], + ... ) + >>> material_2 = cript.Material( + ... name="material 2", + ... identifier=[{"bigsmiles": "material 2 bigsmiles"}], + ... ) + >>> my_inventory = cript.Inventory( + ... name="my inventory name", material=[material_1, material_2] + ... ) + >>> my_collection.inventory = [my_inventory] Returns ------- @@ -217,9 +218,11 @@ def doi(self) -> str: """ The CRIPT DOI for this collection - ```python - my_collection.doi = "10.1038/1781168a0" - ``` + Examples + -------- + >>> import cript + >>> my_collection = cript.Collection(name="my collection name") + >>> my_collection.doi = "10.1038/1781168a0" Returns ------- @@ -253,11 +256,22 @@ def citation(self) -> List[Any]: Examples -------- - ```python - my_citation = cript.Citation(type="derived_from", reference=simple_reference_node) - - my_collections.citation = my_citations - ``` + >>> import cript + >>> my_collection = cript.Collection(name="my collection name") + >>> my_reference = cript.Reference( + ... type="journal_article", + ... title="title", + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="derived_from", reference=my_reference) + >>> my_collection.citation = [my_citation] Returns ------- diff --git a/src/cript/nodes/primary_nodes/computation.py b/src/cript/nodes/primary_nodes/computation.py index 435eeaa47..b318e95a0 100644 --- a/src/cript/nodes/primary_nodes/computation.py +++ b/src/cript/nodes/primary_nodes/computation.py @@ -115,9 +115,8 @@ def __init__( Examples -------- - ```python - my_computation = cript.Computation(name="my computation name", type="analysis") - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") Returns ------- @@ -168,9 +167,9 @@ def type(self) -> str: Examples -------- - ```python - my_computation.type = type="analysis" - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_computation.type = type="analysis" Returns ------- @@ -206,20 +205,17 @@ def input_data(self) -> List[Any]: Examples -------- - ```python - # create file node - my_file = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary" - ) - - # create a data node - my_input_data = cript.Data(name="my data name", type="afm_amp", files=[my_file]) - - my_computation.input_data = [my_input_data] - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_input_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_computation.input_data = [my_input_data] Returns ------- @@ -254,20 +250,17 @@ def output_data(self) -> List[Any]: Examples -------- - ```python - # create file node - my_file = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary" - ) - - # create a data node - my_output_data = cript.Data(name="my data name", type="afm_amp", files=[my_file]) - - my_computation.output_data = [my_output_data] - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_output_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_computation.output_data = [my_output_data] Returns ------- @@ -302,12 +295,11 @@ def software_configuration(self) -> List[Any]: Examples -------- - ```python - # create software configuration node - my_software_configuration = cript.SoftwareConfiguration(software=simple_software_node) - - my_computation.software_configuration = my_software_configuration - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) + >>> my_computation.software_configuration = [my_software_configuration] Returns ------- @@ -342,12 +334,10 @@ def condition(self) -> List[Any]: Examples -------- - ```python - # create a condition node - my_condition = cript.Condition(key="atm", type="min", value=1) - - my_computation.condition = my_condition - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_condition = cript.Condition(key="atm", type="min", value=1) + >>> my_computation.condition = [my_condition] Returns ------- @@ -381,12 +371,12 @@ def prerequisite_computation(self) -> Optional["Computation"]: Examples -------- - ```python - # create computation node for prerequisite_computation - my_prerequisite_computation = cript.Computation(name="my prerequisite computation name", type="data_fit") - - my_computation.prerequisite_computation = my_prerequisite_computation - ``` + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_prerequisite_computation = cript.Computation( + ... name="my prerequisite computation name", type="data_fit" + ... ) + >>> my_computation.prerequisite_computation = my_prerequisite_computation Returns ------- @@ -418,22 +408,18 @@ def citation(self) -> List[Any]: """ List of citations - Examples - -------- - ```python - # create a reference node for the citation - my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") - - # create a reference - my_citation = cript.Citation(type="derived_from", reference=my_reference) - - my_computation.citation = [my_citation] - ``` + Examples + -------- + >>> import cript + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_citation = cript.Citation(type="derived_from", reference=my_reference) + >>> my_computation.citation = [my_citation] - Returns - ------- - List[Citation] - list of citations for this computation node + Returns + ------- + List[Citation] + list of citations for this computation node """ return self._json_attrs.citation.copy() # type: ignore diff --git a/src/cript/nodes/primary_nodes/computation_process.py b/src/cript/nodes/primary_nodes/computation_process.py index 392b91da4..5ab289dd1 100644 --- a/src/cript/nodes/primary_nodes/computation_process.py +++ b/src/cript/nodes/primary_nodes/computation_process.py @@ -143,42 +143,30 @@ def __init__( Examples -------- - ```python - - # create file node for input data node - data_files = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary" - ) - - # create input data node - input_data = cript.Data(name="my data name", type="afm_amp", files=[data_files]) - - # Material node for Quantity node - my_material = cript.Material( - name="my material", - identifier=[{"alternative_names": "my material alternative name"}] - ) - - # create quantity node - my_quantity = cript.Quantity(key="mass", value=1.23, unit="gram") - - # create ingredient node - ingredient = cript.Ingredient( - material=my_material, - quantities=[my_quantity], - ) - - # create computational process node - my_computational_process = cript.ComputationalProcess( - name="my computational process name", - type="cross_linking", - input_data=[input_data], - ingredient=[ingredient], - ) - ``` + >>> import cript + >>> data_files = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> input_data = cript.Data(name="my data name", type="afm_amp", file=[data_files]) + >>> my_material = cript.Material( + ... name="my material", + ... identifier=[{"alternative_names": "my material alternative name"}] + ... ) + >>> my_quantity = cript.Quantity(key="mass", value=1.23, unit="kg") + >>> ingredient = cript.Ingredient( + ... material=my_material, + ... quantity=[my_quantity], + ... ) + >>> my_computation_process = cript.ComputationProcess( + ... name="my computational process name", + ... type="cross_linking", + ... input_data=[input_data], + ... ingredient=[ingredient], + ... ) Parameters @@ -211,8 +199,6 @@ def __init__( """ super().__init__(name=name, notes=notes, **kwargs) - # TODO validate type from vocab - if input_data is None: input_data = [] @@ -257,9 +243,8 @@ def type(self) -> str: Examples -------- - ```python - my_computational_process.type = "DPD" - ``` + >>> import cript + >>> my_computation_process.type = "DPD" # doctest: +SKIP Returns ------- @@ -286,7 +271,6 @@ def type(self, new_type: str) -> None: ------- None """ - # TODO check computational_process type with CRIPT controlled vocabulary new_attrs = replace(self._json_attrs, type=new_type) self._update_json_attrs_if_valid(new_attrs) @@ -298,21 +282,16 @@ def input_data(self) -> List[Any]: Examples -------- - ```python - # create file node for the data node - my_file = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary" - ) - - # create input data node - my_input_data = cript.Data(name="my input data name", type="afm_amp", files=[my_file]) - - # set computational process data node - my_computation.input_data = my_input_data - ``` + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... data_dictionary="my file's data dictionary", + ... extension=".csv", + ... ) + >>> my_input_data = cript.Data(name="my input data name", type="afm_amp", file=[my_file]) + >>> my_computation_process.input_data = [my_input_data] # doctest: +SKIP Returns ------- @@ -346,21 +325,16 @@ def output_data(self) -> List[Any]: Examples -------- - ```python - # create file node for the data node - my_file = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary" - ) - - # create input data node - my_output_data = cript.Data(name="my output data name", type="afm_amp", files=[my_file]) - - # set computational process data node - my_computation.output_data = my_input_data - ``` + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_output_data = cript.Data(name="my output data name", type="afm_amp", file=[my_file]) + >>> my_computation_process.output_data = [my_output_data] # doctest: +SKIP Returns ------- @@ -394,15 +368,15 @@ def ingredient(self) -> List[Any]: Examples -------- - ```python - # create ingredient node - my_ingredient = cript.Ingredient( - material=simple_material_node, - quantities=[simple_quantity_node], - ) - - my_computational_process.ingredient = my_ingredient - ``` + >>> import cript + >>> my_material = cript.Material(name="my material", identifier=[{"bigsmiles": "123456"}]) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, quantity=[my_quantity], keyword=["catalyst"] + ... ) + >>> my_computation_process.ingredient = [my_ingredient] # doctest: +SKIP Returns ------- @@ -436,12 +410,10 @@ def software_configuration(self) -> List[Any]: Examples -------- - ```python - # create software configuration node - my_software_configuration = cript.SoftwareConfiguration(software=simple_software_node) - - my_computational_process.software_configuration = my_software_configuration - ``` + >>> import cript + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) + >>> my_computation_process.software_configuration = [my_software_configuration] # doctest: +SKIP Returns ------- @@ -475,13 +447,9 @@ def condition(self) -> List[Any]: Examples -------- - ```python - # create condition node - my_condition = cript.Condition(key="atm", type="min", value=1) - - my_computational_process.condition = [my_condition] - - ``` + >>> import cript + >>> my_condition = cript.Condition(key="atm", type="min", value=1) + >>> my_computation_process.condition = [my_condition] # doctest: +SKIP Returns ------- @@ -515,15 +483,10 @@ def citation(self) -> List[Any]: Examples -------- - ```python - # create a reference node for the citation - my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") - - # create a reference - my_citation = cript.Citation(type="derived_from", reference=my_reference) - - my_computational_process.citation = [my_citation] - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_citation = cript.Citation(type="derived_from", reference=my_reference) + >>> my_computation_process.citation = [my_citation] # doctest: +SKIP Returns ------- @@ -557,12 +520,9 @@ def property(self) -> List[Any]: Examples -------- - ```python - # create a property node - my_property = cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram") - - my_computational_process.property = [my_property] - ``` + >>> import cript + >>> my_property = cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram") + >>> my_computation_process.property = [my_property] # doctest: +SKIP Returns ------- diff --git a/src/cript/nodes/primary_nodes/data.py b/src/cript/nodes/primary_nodes/data.py index 4fb61e0e3..51edde3f5 100644 --- a/src/cript/nodes/primary_nodes/data.py +++ b/src/cript/nodes/primary_nodes/data.py @@ -30,22 +30,17 @@ class Data(PrimaryBaseNode): | citation | [Citation](../subobjects/citation.md) | | reference to a book, paper, or scholarly work | False | | notes | str | "my awesome notes" | miscellaneous information, or custom data structure | False | - Example + Examples -------- - ```python - # create file node - cript.File( - name="my file node name", - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary" - ) - - - # create data node with required arguments - my_data = cript.Data(name="my data name", type="afm_amp", file=[simple_file_node]) - ``` + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) ## JSON Representation ```json @@ -107,21 +102,18 @@ def __init__( """ Examples -------- - ```python - # create file nodes for the data node - my_file = cript.File( - source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", - type="calibration", - extension=".pdf", - ) - - # create data node and add the file node to it - my_data = cript.Data( - name="my data node name", - type="afm_amp", - file=my_file, - ) - ``` + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", + ... type="calibration", + ... extension=".pdf", + ... ) + >>> my_data = cript.Data( + ... name="my data node name", + ... type="afm_amp", + ... file=[my_file], + ... ) Parameters ---------- @@ -151,7 +143,6 @@ def __init__( Returns ------- None - """ super().__init__(name=name, notes=notes, **kwargs) @@ -196,11 +187,18 @@ def type(self) -> str: """ The data type must come from [CRIPT data type vocabulary](https://app.criptapp.org/vocab/data_type) - Example - ------- - ```python - data.type = "afm_height" - ``` + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_data.type = "nmr_h1" Returns ------- @@ -214,7 +212,7 @@ def type(self) -> str: def type(self, new_data_type: str) -> None: """ set the data type. - The data type must come from [CRIPT data type vocabulary]() + The data type must come from [CRIPT data type vocabulary](https://app.criptapp.org/vocab/data_type) Parameters ---------- @@ -225,7 +223,6 @@ def type(self, new_data_type: str) -> None: ------- None """ - # TODO validate that the data type is valid from CRIPT controlled vocabulary new_attrs = replace(self._json_attrs, type=new_data_type) self._update_json_attrs_if_valid(new_attrs) @@ -233,24 +230,27 @@ def type(self, new_data_type: str) -> None: @beartype def file(self) -> List[Any]: """ - get the list of files for this data node + get the list of [files](../../supporting_nodes/file) for this data node Examples -------- - ```python - # create a list of file nodes - my_new_files = [ - # file with link source - cript.File( - source="https://pubs.acs.org/doi/10.1021/acscentsci.3c00011", - type="computation_config", - extension=".pdf", - data_dictionary="my second file data dictionary", - ), - ] - - data_node.file = my_new_files - ``` + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_new_file = cript.File( + ... name="my new file node name", + ... source="path/to/local/file", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data.file += [my_new_file] Returns ------- @@ -281,7 +281,21 @@ def file(self, new_file_list: List[Any]) -> None: @beartype def sample_preparation(self) -> Union[Any, None]: """ - The sample preparation for this data node + The [sample preparation](../process) for this data node + + Examples + -------- + >>> import cript + >>> my_new_files = cript.File( + ... name="my file node name", + ... source="https://pubs.acs.org/doi/10.1021/acscentsci.3c00011", + ... type="computation_config", + ... extension=".pdf", + ... data_dictionary="my data dictionary", + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_new_files]) + >>> my_sample_preparation = cript.Process(name="my sample preparation name", type="affinity_pure") + >>> my_data.sample_preparation = my_sample_preparation Returns ------- @@ -312,7 +326,21 @@ def sample_preparation(self, new_sample_preparation: Union[Any, None]) -> None: @beartype def computation(self) -> List[Any]: """ - list of computation nodes for this material node + list of [computation nodes](../computation/) for this material node + + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_data.computation = [my_computation] Returns ------- @@ -343,7 +371,53 @@ def computation(self, new_computation_list: List[Any]) -> None: @beartype def computation_process(self) -> Union[Any, None]: """ - The computation_process for this data node + The [computation_process](../computation_process) for this data node + + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data( + ... name="my data name", + ... type="afm_amp", + ... file=[my_file] + ... ) + >>> my_file_for_second_data_node = cript.File( + ... name="my second file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_second_data_node = cript.Data( + ... name="my data name", + ... type="afm_amp", + ... file=[my_file_for_second_data_node] + ... ) + >>> my_material = cript.Material( + ... name="my material name", + ... identifier=[{"bigsmiles": "123456"}] + ... ) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, + ... quantity=[my_quantity], + ... keyword=["catalyst"], + ... ) + >>> my_computational_process = cript.ComputationProcess( + ... name="my computational process node name", + ... type="cross_linking", + ... input_data=[my_second_data_node], + ... ingredient=[my_ingredient], + ... ) Returns ------- @@ -373,7 +447,21 @@ def computation_process(self, new_computation_process: Union[Any, None]) -> None @beartype def material(self) -> List[Any]: """ - List of materials for this node + List of [materials](../material) for this node + + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_material = cript.Material(name="my material name", identifier=[{"bigsmiles": "123456"}]) + >>> my_data.material = [my_material] Returns ------- @@ -405,6 +493,20 @@ def process(self) -> List[Any]: """ list of [Process nodes](./process.md) for this data node + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_data.process = [my_process] + Notes ----- Please note that while the process attribute of the data node is currently set to `Any` @@ -442,18 +544,21 @@ def citation(self) -> List[Any]: """ List of [citation](../../subobjects/citation) within the data node - Example - ------- - ```python - # create a reference node - my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") - - # create a citation list to house all the reference nodes - my_citation = cript.Citation(type="derived_from", reference=my_reference) - - # add citations to data node - my_data.citation = my_citations - ``` + Examples + -------- + >>> import cript + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary" + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_citation = cript.Citation(type="derived_from", reference=my_reference) + >>> my_data.citation = [my_citation] Returns ------- diff --git a/src/cript/nodes/primary_nodes/experiment.py b/src/cript/nodes/primary_nodes/experiment.py index fb9812eb7..17e683490 100644 --- a/src/cript/nodes/primary_nodes/experiment.py +++ b/src/cript/nodes/primary_nodes/experiment.py @@ -27,7 +27,7 @@ class Experiment(PrimaryBaseNode): | notes | str | miscellaneous information, or custom data structure | False | - ## Subobjects + ## Sub-objects An [Experiment node](https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf#page=9) can be thought as a folder/bucket that can hold: @@ -90,6 +90,11 @@ def __init__( """ create an Experiment node + Examples + -------- + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + Parameters ---------- name: str @@ -109,13 +114,6 @@ def __init__( notes: str default="" notes for the experiment node - Examples - -------- - ```python - # create an experiment node with all possible arguments - my_experiment = cript.Experiment(name="my experiment name") - ``` - Returns ------- None @@ -158,12 +156,12 @@ def process(self) -> List[Any]: """ List of process for experiment - ```python - # create a simple process node - my_process = cript.Process(name="my process name", type="affinity_pure") - - my_experiment.process = [my_process] - ``` + Examples + -------- + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_experiment.process = [my_process] Returns ------- @@ -198,13 +196,10 @@ def computation(self) -> List[Any]: Examples -------- - ```python - # create computation node - my_computation = cript.Computation(name="my computation name", type="analysis") - - # add computation node to experiment node - simple_experiment_node.computation = [simple_computation_node] - ``` + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_experiment.computation = [my_computation] Returns ------- @@ -239,17 +234,32 @@ def computation_process(self) -> List[Any]: Examples -------- - ```python - my_computation_process = cript.ComputationalProcess( - name="my computational process name", - type="cross_linking", # must come from CRIPT Controlled Vocabulary - input_data=[input_data], # input data is another data node - ingredients=[ingredients], # output data is another data node - ) - - # add computation_process node to experiment node - my_experiment.computation_process = [my_computational_process] - ``` + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_file = cript.File( + ... name="my file node", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary", + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_material = cript.Material( + ... name="my material name", identifier=[{"bigsmiles": "123456"}] + ... ) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, quantity=[my_quantity], keyword=["catalyst"] + ... ) + >>> my_computation_process = cript.ComputationProcess( + ... name="my computational process name", + ... type="cross_linking", # must come from CRIPT Controlled Vocabulary + ... input_data=[my_data], # input data is another data node + ... ingredient=[my_ingredient], # output data is another data node + ... ) + >>> my_experiment.computation_process = [my_computation_process] Returns ------- @@ -284,20 +294,17 @@ def data(self) -> List[Any]: Examples -------- - ```python - # create a simple file node - my_file = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary", - ) - - # create a simple data node - my_data = cript.Data(name="my data name", type="afm_amp", files=[my_file]) - - my_experiment.data = my_data - ``` + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary", + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_experiment.data = [my_data] Returns ------- @@ -332,9 +339,9 @@ def funding(self) -> List[str]: Examples -------- - ```python - my_experiment.funding = ["National Science Foundation", "IRIS", "NIST"] - ``` + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_experiment.funding = ["National Science Foundation", "IRIS", "NIST"] Returns ------- @@ -369,13 +376,22 @@ def citation(self) -> List[Any]: Examples -------- - ```python - # create citation node - my_citation = cript.Citation(type="derived_from", reference=simple_reference_node) - - # add citation to experiment - my_experiment.citations = [my_citation] - ``` + >>> import cript + >>> my_experiment = cript.Experiment(name="my experiment name") + >>> my_reference = cript.Reference( + ... type="journal_article", + ... title="title", + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="derived_from", reference=my_reference) + >>> my_experiment.citation = [my_citation] Returns ------- diff --git a/src/cript/nodes/primary_nodes/inventory.py b/src/cript/nodes/primary_nodes/inventory.py index 0bc824f48..511d99d8e 100644 --- a/src/cript/nodes/primary_nodes/inventory.py +++ b/src/cript/nodes/primary_nodes/inventory.py @@ -70,22 +70,18 @@ def __init__(self, name: str, material: List[Material], notes: str = "", **kwarg Examples -------- - ```python - material_1 = cript.Material( - name="material 1", - identifier=[{"alternative_names": "material 1 alternative name"}], - ) - - material_2 = cript.Material( - name="material 2", - identifier=[{"alternative_names": "material 2 alternative name"}], - ) - - # instantiate inventory node - my_inventory = cript.Inventory( - name="my inventory name", material=[material_1, material_2] - ) - ``` + >>> import cript + >>> material_1 = cript.Material( + ... name="material 1", + ... identifier=[{"bigsmiles": "material 1 bigsmiles"}], + ... ) + >>> material_2 = cript.Material( + ... name="material 2", + ... identifier=[{"bigsmiles": "material 2 bigsmiles"}], + ... ) + >>> my_inventory = cript.Inventory( + ... name="my inventory name", material=[material_1, material_2] + ... ) Parameters ---------- @@ -113,14 +109,17 @@ def material(self) -> List[Material]: Examples -------- - ```python - material_3 = cript.Material( - name="new material 3", - identifier=[{"alternative_names": "new material 3 alternative name"}], - ) - - my_inventory.material = [my_material_3] - ``` + >>> import cript + >>> my_material = cript.Material( + ... name="my material", + ... identifier=[{"bigsmiles": "my bigsmiles"}], + ... ) + >>> my_inventory = cript.Inventory(name="my inventory", material=[my_material]) + >>> new_material = cript.Material( + ... name="new material", + ... identifier=[{"bigsmiles": "my bigsmiles"}], + ... ) + >>> my_inventory.material = [new_material] Returns ------- diff --git a/src/cript/nodes/primary_nodes/material.py b/src/cript/nodes/primary_nodes/material.py index 473fd46da..4e7fffa4a 100644 --- a/src/cript/nodes/primary_nodes/material.py +++ b/src/cript/nodes/primary_nodes/material.py @@ -16,12 +16,12 @@ class Material(PrimaryBaseNode): ## Attributes | attribute | type | example | description | required | vocab | |---------------------------|----------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------|-------------|-------| - | identifier | list[Identifier] | | material identifiers | True | | + | identifier | list[Identifier] | | material identifiers | True | | | component | list[[Material](./)] | | list of component that make up the mixture | | | | property | list[[Property](../../subobjects/property)] | | material properties | | | | process | [Process](../process) | | process node that made this material | | | | parent_material | [Material](./) | | material node that this node was copied from | | | - | computational_ forcefield | [Computation Forcefield](../../subobjects/computational_forcefield) | | computation forcefield | Conditional | | + | computational_forcefield | [Computation Forcefield](../../subobjects/computational_forcefield) | | computation forcefield | Conditional | | | keyword | list[str] | [thermoplastic, homopolymer, linear, polyolefins] | words that classify the material | | True | | notes | str | "my awesome notes" | miscellaneous information, or custom data structure | | True | @@ -90,6 +90,14 @@ def __init__( """ create a material node + Examples + -------- + >>> import cript + >>> my_material = cript.Material( + ... name="my component material 1", + ... identifier=[{"amino_acid": "component 1 alternative name"}], + ... ) + Parameters ---------- name: str @@ -130,50 +138,20 @@ def __init__( keyword=keyword, ) - @property - @beartype - def name(self) -> str: - """ - material name - - Examples - ```python - my_material.name = "my new material" - ``` - - Returns - ------- - str - material name - """ - return self._json_attrs.name - - @name.setter - @beartype - def name(self, new_name: str) -> None: - """ - set the name of the material - - Parameters - ---------- - new_name: str - - Returns - ------- - None - """ - new_attrs = replace(self._json_attrs, name=new_name) - self._update_json_attrs_if_valid(new_attrs) - @property @beartype def identifier(self) -> List[Dict[str, str]]: """ get the identifiers for this material - ```python - my_material.identifier = {"alternative_names": "my material alternative name"} - ``` + Examples + -------- + >>> import cript + >>> my_material = cript.Material( + ... name="my component material 1", + ... identifier=[{"smiles": "component 1 smiles"}], + ... ) + >>> my_material.identifier = [{"smiles": "my material alternative name"}] [material identifier key](https://app.criptapp.org/vocab/material_identifier_key) must come from CRIPT controlled vocabulary @@ -212,27 +190,23 @@ def component(self) -> List["Material"]: list of components ([material nodes](./)) that make up this material Examples - -------- - ```python - # material component - my_component = [ - # create material node - cript.Material( - name="my component material 1", - identifier=[{"alternative_names": "component 1 alternative name"}], - ), - - # create material node - cript.Material( - name="my component material 2", - identifier=[{"alternative_names": "component 2 alternative name"}], - ), - ] - - - identifier = [{"alternative_names": "my material alternative name"}] - my_material = cript.Material(name="my material", component=my_component, identifier=identifier) - ``` + --------- + >>> import cript + >>> my_components = [ + ... cript.Material( + ... name="my component material 1", + ... identifier=[{"smiles": "my material smiles"}], + ... ), + ... cript.Material( + ... name="my component material 2", + ... identifier=[{"vendor": "my material vendor"}], + ... ), + ... ] + >>> my_mixed_material = cript.Material( + ... name="my material", + ... component=my_components, + ... identifier=[{"bigsmiles": "123456"}] + ... ) Returns ------- @@ -295,6 +269,18 @@ def computational_forcefield(self) -> Any: """ list of [computational_forcefield](../../subobjects/computational_forcefield) for this material node + Examples + -------- + >>> import cript + >>> my_material = cript.Material( + ... name="my component material 1", identifier=[{"smiles": "my smiles"}] + ... ) + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_material.computational_forcefield = my_computational_forcefield + Returns ------- List[ComputationForcefield] @@ -328,16 +314,13 @@ def keyword(self) -> List[str]: the material keyword must come from the [CRIPT controlled vocabulary](https://app.criptapp.org/vocab/material_keyword) - ```python - identifier = [{"alternative_names": "my material alternative name"}] - - # keyword - material_keyword = ["acetylene", "acrylate", "alternating"] - - my_material = cript.Material( - name="my material", keyword=material_keyword, identifier=identifier - ) - ``` + Examples + -------- + >>> import cript + >>> my_material = cript.Material( + ... name="my material", identifier=[{"inchi": "my material inchi"}] + ... ) + >>> my_material.keyword = ["acetylene", "acrylate", "alternating"] Returns ------- @@ -380,12 +363,15 @@ def property(self) -> List[Any]: """ list of material [property](../../subobjects/property) - ```python - # property subobject - my_property = cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram") - - my_material.property = my_property - ``` + Examples + -------- + >>> import cript + >>> my_material = cript.Material( + ... name="my component material 1", + ... identifier=[{"smiles": "component 1 smiles"}], + ... ) + >>> my_property = cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram") + >>> my_material.property = [my_property] Returns ------- diff --git a/src/cript/nodes/primary_nodes/process.py b/src/cript/nodes/primary_nodes/process.py index cdc6dcfa8..a8d9de573 100644 --- a/src/cript/nodes/primary_nodes/process.py +++ b/src/cript/nodes/primary_nodes/process.py @@ -51,7 +51,6 @@ class Process(PrimaryBaseNode): "uuid":"f8ef33f3-677a-40f3-b24e-65ab2c99d796" } ``` - """ @dataclass(frozen=True) @@ -96,9 +95,10 @@ def __init__( """ create a process node - ```python - my_process = cript.Process(name="my process name", type="affinity_pure") - ``` + Examples + -------- + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") Parameters ---------- @@ -184,9 +184,9 @@ def type(self) -> str: Examples -------- - ```python - my_process.type = "affinity_pure" - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_process.type = "affinity_pure" Returns ------- @@ -221,14 +221,17 @@ def ingredient(self) -> List[Any]: Examples --------- - ```python - my_ingredients = cript.Ingredient( - material=simple_material_node, - quantities=[simple_quantity_node], - ) - - my_process.ingredient = [my_ingredients] - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_identifier = [{"bigsmiles": "123456"}] + >>> my_material = cript.Material(name="my material", identifier=my_identifier) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, quantity=[my_quantity], keyword=["catalyst"] + ... ) + >>> my_process.ingredient = [my_ingredient] Returns ------- @@ -252,8 +255,6 @@ def ingredient(self, new_ingredient_list: List[Any]) -> None: ------- None """ - # TODO need to validate with CRIPT controlled vocabulary - # and if invalid then raise an error immediately new_attrs = replace(self._json_attrs, ingredient=new_ingredient_list) self._update_json_attrs_if_valid(new_attrs) @@ -265,9 +266,9 @@ def description(self) -> str: Examples -------- - ```python - my_process.description = "To oven-dried 20 mL glass vial, 5 mL of styrene and 10 ml of toluene was added" - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_process.description = "To oven-dried 20 mL glass vial, 5 mL of styrene and 10 ml of toluene was added" Returns ------- @@ -300,6 +301,13 @@ def equipment(self) -> List[Any]: """ List of [equipment](../../subobjects/equipment) used for this process + Examples + -------- + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_equipment = cript.Equipment(key="burner") + >>> my_process.equipment = [my_equipment] + Returns ------- List[Equipment] @@ -331,6 +339,16 @@ def product(self) -> List[Any]: """ List of product (material nodes) for this process + Examples + -------- + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_product_material = cript.Material( + ... name="my product material", + ... identifier=[{"amino_acid": "my material product amino_acid"}], + ... ) + >>> my_process.product = [my_product_material] + Returns ------- List[Material] @@ -364,9 +382,13 @@ def waste(self) -> List[Any]: Examples -------- - ```python - my_process.waste = my_waste_material - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_waste_material = cript.Material( + ... name="my waste material", + ... identifier=[{"bigsmiles": "123456"}], + ... ) + >>> my_process.waste = [my_waste_material] Returns ------- @@ -401,15 +423,13 @@ def prerequisite_process(self) -> List["Process"]: Examples -------- - ```python - - my_prerequisite_process = [ - cript.Process(name="prerequisite processes 1", type="blow_molding"), - cript.Process(name="prerequisite processes 2", type="centrifugation"), - ] - - my_process.prerequisite_process = my_prerequisite_process - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_prerequisite_process = [ + ... cript.Process(name="prerequisite processes 1", type="blow_molding"), + ... cript.Process(name="prerequisite processes 2", type="centrifugation"), + ... ] + >>> my_process.prerequisite_process = my_prerequisite_process Returns ------- @@ -442,13 +462,11 @@ def condition(self) -> List[Any]: List of condition present for this process Examples - ------- - ```python - # create condition node - my_condition = cript.Condition(key="atm", type="min", value=1) - - my_process.condition = [my_condition] - ``` + -------- + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_condition = cript.Condition(key="atm", type="min", value=1) + >>> my_process.condition = [my_condition] Returns ------- @@ -479,9 +497,14 @@ def condition(self, new_condition_list: List[Any]) -> None: def keyword(self) -> List[str]: """ List of keyword for this process - [Process keyword](https://app.criptapp.org/vocab/process_keyword/) must come from CRIPT controlled vocabulary + Examples + -------- + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_process.keyword = ["self_assembly"] + Returns ------- List[str] @@ -512,19 +535,15 @@ def keyword(self, new_keyword_list: List[str]) -> None: @beartype def citation(self) -> List[Any]: """ - List of citation for this process + List of [citation](../subobjects/citation.md) for this process Examples -------- - ```python - # crate reference node for this citation - my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") - - # create citation node - my_citation = cript.Citation(type="derived_from", reference=my_reference) - - my_process.citation = [my_citation] - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_citation = cript.Citation(type="derived_from", reference=my_reference) + >>> my_process.citation = [my_citation] Returns ------- @@ -559,12 +578,10 @@ def property(self) -> List[Any]: Examples -------- - ```python - # create property node - my_property = cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram") - - my_process.properties = [my_property] - ``` + >>> import cript + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_property = cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram") + >>> my_process.property = [my_property] Returns ------- diff --git a/src/cript/nodes/primary_nodes/project.py b/src/cript/nodes/primary_nodes/project.py index 4d66331f9..4da8b427b 100644 --- a/src/cript/nodes/primary_nodes/project.py +++ b/src/cript/nodes/primary_nodes/project.py @@ -69,7 +69,13 @@ class JsonAttributes(PrimaryBaseNode.JsonAttributes): @beartype def __init__(self, name: str, collection: Optional[List[Collection]] = None, material: Optional[List[Material]] = None, notes: str = "", **kwargs): """ - Create a Project node with Project name and Group + Create a Project node with Project name + + Examples + -------- + >>> import cript + >>> my_project = cript.Project(name="my Project name") + Parameters ---------- @@ -98,14 +104,14 @@ def __init__(self, name: str, collection: Optional[List[Collection]] = None, mat self._json_attrs = replace(self._json_attrs, name=name, collection=collection, material=material) self.validate() - def validate(self, api=None, is_patch=False): + def validate(self, api=None, is_patch=False, force_validation: bool = False): from cript.nodes.exceptions import ( CRIPTOrphanedMaterialError, get_orphaned_experiment_exception, ) # First validate like other nodes - super().validate(api=api, is_patch=is_patch) + super().validate(api=api, is_patch=is_patch, force_validation=force_validation) # Check graph for orphaned nodes, that should be listed in project # Project.materials should contain all material nodes @@ -161,13 +167,10 @@ def collection(self) -> List[Collection]: Examples -------- - ```python - my_new_collection = cript.Collection( - name="my collection name", experiments=[my_experiment_node] - ) - - my_project.collection = my_new_collection - ``` + >>> import cript + >>> my_project = cript.Project(name="my Project name") + >>> my_new_collection = cript.Collection(name="my collection name") + >>> my_project.collection = [my_new_collection] Returns ------- @@ -201,12 +204,11 @@ def material(self) -> List[Material]: Examples -------- - ```python - identifier = [{"alternative_names": "my material alternative name"}] - my_material = cript.Material(name="my material", identifier=identifier) - - my_project.material = [my_material] - ``` + >>> import cript + >>> my_project = cript.Project(name="my Project name") + >>> identifier = [{"bigsmiles": "my big smiles"}] + >>> my_material = cript.Material(name="my material", identifier=identifier) + >>> my_project.material = [my_material] Returns ------- diff --git a/src/cript/nodes/primary_nodes/reference.py b/src/cript/nodes/primary_nodes/reference.py index e4ba0603f..996bf7e0d 100644 --- a/src/cript/nodes/primary_nodes/reference.py +++ b/src/cript/nodes/primary_nodes/reference.py @@ -12,7 +12,6 @@ class Reference(UUIDBaseNode): The [Reference node](https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf#page=15) - contains the metadata for a literature publication, book, or anything external to CRIPT. The reference node does NOT contain the base attributes. @@ -24,7 +23,7 @@ class Reference(UUIDBaseNode): |-----------|-----------|--------------------------------------------|-----------------------------------------------|---------------|-------| | type | str | journal_article | type of literature | True | True | | title | str | 'Living' Polymers | title of publication | True | | - | author | list[str] | Michael Szwarc | list of authors | | | + | author | list[str] | Michael Szwarc | list of authors | | | | journal | str | Nature | journal of the publication | | | | publisher | str | Springer | publisher of publication | | | | year | int | 1956 | year of publication | | | @@ -115,13 +114,17 @@ def __init__( """ create a reference node - reference type must come from CRIPT controlled vocabulary + Examples + -------- + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") Parameters ---------- type: str type of literature. - The reference type must come from CRIPT controlled vocabulary + The [reference type](https://app.criptapp.org/vocab/reference_type/) + must come from CRIPT controlled vocabulary title: str title of publication author: List[str] default="" @@ -149,13 +152,6 @@ def __init__( website: str default="" website where the publication can be accessed - - Examples - -------- - ```python - my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") - ``` - Returns ------- None @@ -185,9 +181,9 @@ def type(self) -> str: Examples -------- - ```python - my_reference.type = "journal_article" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.type = "web_site" Returns ------- @@ -224,9 +220,9 @@ def title(self) -> str: Examples -------- - ```python - my_reference.title = "my new title" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.title = "my new title" Returns ------- @@ -260,9 +256,9 @@ def author(self) -> List[str]: Examples -------- - ```python - my_reference.author = ["Bradley D. Olsen", "Dylan Walsh"] - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.author += ["Navid Hariri"] Returns ------- @@ -296,9 +292,9 @@ def journal(self) -> str: Examples -------- - ```python - my_reference.journal = "my new journal" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.journal = "my new journal" Returns ------- @@ -332,9 +328,9 @@ def publisher(self) -> str: Examples -------- - ```python - my_reference.publisher = "my new publisher" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.publisher = "my new publisher" Returns ------- @@ -368,9 +364,9 @@ def year(self) -> Union[int, None]: Examples -------- - ```python - my_reference.year = 2023 - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.year = 2023 Returns ------- @@ -404,9 +400,9 @@ def volume(self) -> Union[int, None]: Examples -------- - ```python - my_reference.volume = 1 - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.volume = 1 Returns ------- @@ -440,9 +436,9 @@ def issue(self) -> Union[int, None]: Examples -------- - ```python - my_reference.issue = 2 - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.issue = 2 Returns ------- @@ -475,9 +471,9 @@ def pages(self) -> List[int]: Examples -------- - ```python - my_reference.pages = [123, 456] - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.pages = [123, 456] Returns ------- @@ -510,9 +506,9 @@ def doi(self) -> str: Examples -------- - ```python - my_reference.doi = "100.1038/1781168a0" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.doi = "100.1038/1781168a0" Returns ------- @@ -533,9 +529,9 @@ def doi(self, new_doi: str) -> None: Examples -------- - ```python - my_reference.doi = "100.1038/1781168a0" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.doi = "100.1038/1781168a0" Returns ------- @@ -551,9 +547,10 @@ def issn(self) -> str: The international standard serial number (ISSN) for this reference node Examples - ```python - my_reference.issn = "1456-4687" - ``` + --------- + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.issn = "1456-4687" Returns ------- @@ -587,9 +584,9 @@ def arxiv_id(self) -> str: Examples -------- - ```python - my_reference.arxiv_id = "1501" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.arxiv_id = "1501" Returns ------- @@ -623,9 +620,9 @@ def pmid(self) -> Union[int, None]: Examples -------- - ```python - my_reference.pmid = 12345678 - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.pmid = 12345678 Returns ------- @@ -660,9 +657,9 @@ def website(self) -> str: Examples -------- - ```python - my_reference.website = "https://criptapp.org" - ``` + >>> import cript + >>> my_reference = cript.Reference(type="journal_article", title="'Living' Polymers") + >>> my_reference.website = "https://criptapp.org" Returns ------- diff --git a/src/cript/nodes/subobjects/algorithm.py b/src/cript/nodes/subobjects/algorithm.py index e65fc6c53..b5cc59069 100644 --- a/src/cript/nodes/subobjects/algorithm.py +++ b/src/cript/nodes/subobjects/algorithm.py @@ -76,14 +76,16 @@ class JsonAttributes(UUIDBaseNode.JsonAttributes): def __init__(self, key: str, type: str, parameter: Optional[List[Parameter]] = None, citation: Optional[List[Citation]] = None, **kwargs): # ignored """ - create algorithm sub-object + Create algorithm sub-object Parameters ---------- key : str - algorithm key must come from [CRIPT controlled vocabulary]() + algorithm key must come from + [CRIPT controlled vocabulary](https://app.criptapp.org/vocab/algorithm_key) type : str - algorithm type must come from [CRIPT controlled vocabulary]() + algorithm type must come from + [CRIPT controlled vocabulary](https://app.criptapp.org/vocab/algorithm_type) parameter : List[Parameter], optional parameter sub-object, by default None citation : List[Citation], optional @@ -91,10 +93,8 @@ def __init__(self, key: str, type: str, parameter: Optional[List[Parameter]] = N Examples -------- - ```python - # create algorithm sub-object - algorithm = cript.Algorithm(key="mc_barostat", type="barostat") - ``` + >>> import cript + >>> my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") Returns ------- @@ -118,9 +118,9 @@ def key(self) -> str: Examples -------- - ```python - algorithm.key = "amorphous_cell_module" - ``` + >>> import cript + >>> my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") + >>> my_algorithm.key = "amorphous_cell_module" Returns ------- @@ -134,7 +134,8 @@ def key(self, new_key: str) -> None: """ set the algorithm key - > Algorithm key must come from [CRIPT Controlled Vocabulary]() + > Algorithm key must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/algorithm_key) Parameters ---------- @@ -149,13 +150,14 @@ def type(self) -> str: """ Algorithm type - > Algorithm type must come from [CRIPT controlled vocabulary]() + > Algorithm type must come from + [CRIPT controlled vocabulary](https://app.criptapp.org/vocab/algorithm_type) Examples -------- - ```python - my_algorithm.type = "integration" - ``` + >>> import cript + >>> my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") + >>> my_algorithm.type = "integration" Returns ------- @@ -176,16 +178,13 @@ def parameter(self) -> List[Parameter]: Examples -------- - ```python - # create parameter sub-object - my_parameter = [ - cript.Parameter("update_frequency", 1000.0, "1/second") - cript.Parameter("damping_time", 1.0, "second") - ] - - # add parameter sub-object to algorithm sub-object - algorithm.parameter = my_parameter - ``` + >>> import cript + >>> my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") + >>> my_parameters = [ + ... cript.Parameter("update_frequency", 1000.0, "1/second"), + ... cript.Parameter("damping_time", 1.0, "second"), + ... ] + >>> my_algorithm.parameter = my_parameters Returns ------- @@ -218,30 +217,27 @@ def citation(self) -> Citation: Examples -------- - ```python - title = "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " - title += "SOft coarse grained Monte-Carlo Acceleration (SOMA)" - - # create reference node - my_reference = cript.Reference( - type="journal_article", - title=title, - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - # create citation sub-object and add reference to it - my_citation = cript.Citation(type="reference, reference==my_reference) - - # add citation to algorithm node - algorithm.citation = my_citation - ``` + >>> import cript + >>> my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... type="journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + >>> my_algorithm.citation = [my_citation] + Returns ------- diff --git a/src/cript/nodes/subobjects/citation.py b/src/cript/nodes/subobjects/citation.py index e3aae8bac..abcba9df5 100644 --- a/src/cript/nodes/subobjects/citation.py +++ b/src/cript/nodes/subobjects/citation.py @@ -72,33 +72,31 @@ def __init__(self, type: str, reference: Reference, **kwargs): Parameters ---------- type : citation type - citation type must come from [CRIPT Controlled Vocabulary]() + citation type must come from [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/citation_type) reference : Reference Reference node Examples ------- - ```python - title = "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " - title += "SOft coarse grained Monte-Carlo Acceleration (SOMA)" - - # create a Reference node for the Citation subobject - my_reference = Reference( - "journal_article", - title=title, - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - # create Citation subobject - my_citation = cript.Citation("reference", my_reference) - ``` + >>> import cript + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... "journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + Returns ------- @@ -119,9 +117,25 @@ def type(self) -> str: Examples -------- - ```python - my_citation.type = "extracted_by_algorithm" - ``` + >>> import cript + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... "journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + >>> my_citation.type = "extracted_by_algorithm" Returns ------- @@ -134,9 +148,7 @@ def type(self) -> str: @beartype def type(self, new_type: str) -> None: """ - set the citation subobject type - - > Note: citation subobject must come from [CRIPT Controlled Vocabulary]() + set the citation sub-object type Parameters ---------- @@ -158,23 +170,41 @@ def reference(self) -> Union[Reference, None]: Examples -------- - ```python - # create a Reference node for the Citation subobject - my_reference = Reference( - "journal_article", - title="my title", - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - my_citation.reference = my_reference - ``` + >>> import cript + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... "journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + >>> my_new_reference = cript.Reference( + ... type="journal_article", + ... title="'Living' Polymers", + ... author=["Dylan J. Walsh", "Bradley D. Olsen"], + ... journal="Nature", + ... publisher="Springer", + ... year=2019, + ... volume=3, + ... issue=5, + ... pages=[123, 456, 789], + ... doi="10.1038/1781168a0", + ... issn="1476-4687", + ... arxiv_id="1501", + ... pmid=12345678, + ... website="https://criptapp.org", + ... ) + >>> my_citation.reference = my_new_reference Returns ------- diff --git a/src/cript/nodes/subobjects/computational_forcefield.py b/src/cript/nodes/subobjects/computational_forcefield.py index 45416f0da..e9176014b 100644 --- a/src/cript/nodes/subobjects/computational_forcefield.py +++ b/src/cript/nodes/subobjects/computational_forcefield.py @@ -101,9 +101,11 @@ def __init__(self, key: str, building_block: str, coarse_grained_mapping: str = Parameters ---------- key : str - type of forcefield key must come from [CRIPT Controlled Vocabulary]() + type of forcefield key must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/computational_forcefield_key) building_block : str - type of computational_forcefield building_block must come from [CRIPT Controlled Vocabulary]() + type of computational_forcefield building_block must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/building_block) coarse_grained_mapping : str, optional atom to beads mapping, by default "" implicit_solvent : str, optional @@ -120,12 +122,11 @@ def __init__(self, key: str, building_block: str, coarse_grained_mapping: str = Examples -------- - ```python - my_computational_forcefield = cript.ComputationalForcefield( - key="opls_aa", - building_block="atom", - ) - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) Returns ------- @@ -163,9 +164,12 @@ def key(self) -> str: Examples -------- - ```python - my_computational_forcefield.key = "amber" - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_computational_forcefield.key = "amber" Returns ------- @@ -203,9 +207,12 @@ def building_block(self) -> str: Examples -------- - ```python - my_computational_forcefield.building_block = "atom" - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_computational_forcefield.building_block = "non_atomistic" Returns ------- @@ -240,9 +247,12 @@ def coarse_grained_mapping(self) -> str: Examples -------- - ```python - my_computational_forcefield.coarse_grained_mapping = "SC3 beads in MARTINI forcefield" - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_computational_forcefield.coarse_grained_mapping = "SC3 beads in MARTINI forcefield" Returns ------- @@ -277,9 +287,12 @@ def implicit_solvent(self) -> str: Examples -------- - ```python - my_computational_forcefield.implicit_solvent = "water" - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_computational_forcefield.implicit_solvent = "water" Returns ------- @@ -310,9 +323,12 @@ def source(self) -> str: Examples -------- - ```python - my_computational_forcefield.source = "package in GROMACS" - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_computational_forcefield.source = "package in GROMACS" Returns ------- @@ -343,9 +359,12 @@ def description(self) -> str: Examples -------- - ```python - my_computational_forcefield.description = "OPLS forcefield with partial charges calculated via the LBCC algorithm" - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_computational_forcefield.description = "OPLS forcefield with partial charges calculated via the LBCC algorithm" Returns ------- @@ -380,24 +399,23 @@ def data(self) -> List[Data]: Examples -------- - ```python - # create file nodes for the data node - my_file = cript.File( - source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", - type="calibration", - extension=".pdf", - ) - - # create data node and add the file node to it - my_data = cript.Data( - name="my data node name", - type="afm_amp", - file=my_file, - ) - - # add data node to computational_forcefield subobject - my_computational_forcefield.data = [my_data] - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", + ... type="calibration", + ... extension=".pdf", + ... ) + >>> my_data = cript.Data( + ... name="my data node name", + ... type="afm_amp", + ... file=[my_file], + ... ) + >>> my_computational_forcefield.data = [my_data] Returns ------- @@ -432,29 +450,29 @@ def citation(self) -> List[Citation]: Examples -------- - ```python - # create reference node for the citation node - title = "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " - title += "SOft coarse grained Monte-Carlo Acceleration (SOMA)" - - my_reference = cript.Reference( - "journal_article", - title=title, - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - # create citation node and add reference node to it - my_citation = cript.Citation(type="reference", reference=my_reference) - - my_computational_forcefield.citation = [my_citation] - ``` + >>> import cript + >>> my_computational_forcefield = cript.ComputationalForcefield( + ... key="opls_aa", + ... building_block="atom", + ... ) + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "SOft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... "journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + >>> my_computational_forcefield.citation = [my_citation] Returns ------- diff --git a/src/cript/nodes/subobjects/condition.py b/src/cript/nodes/subobjects/condition.py index 27f6995cc..acc17f3de 100644 --- a/src/cript/nodes/subobjects/condition.py +++ b/src/cript/nodes/subobjects/condition.py @@ -134,15 +134,13 @@ def __init__( Examples -------- - ```python - # instantiate a Condition sub-object - my_condition = cript.Condition( - key="temperature", - type="value", - value=22, - unit="C", - ) - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) Returns ------- @@ -178,9 +176,14 @@ def key(self) -> str: Examples -------- - ```python - my_condition.key = "energy_threshold" - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.key = "energy_threshold" Returns ------- @@ -195,8 +198,6 @@ def key(self, new_key: str) -> None: """ set this Condition sub-object key - > Condition key must come from [CRIPT Controlled Vocabulary]() - Parameters ---------- new_key : str @@ -217,9 +218,14 @@ def type(self) -> str: Examples -------- - ```python - my_condition.type = "min" - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.type = "min" Returns ------- @@ -254,9 +260,14 @@ def descriptor(self) -> str: Examples -------- - ```python - my_condition.description = "my condition description" - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.descriptor = "my condition description" Returns ------- @@ -289,12 +300,6 @@ def value(self) -> Optional[Union[Number, str]]: """ value or quantity - Examples - ------- - ```python - my_condition.value = 10 - ``` - Returns ------- Union[Number, None] @@ -315,9 +320,14 @@ def set_value(self, new_value: Union[Number, str], new_unit: str) -> None: Examples -------- - ```python - my_condition.set_value(new_value=1, new_unit="gram") - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.set_value(new_value=1, new_unit="gram") Returns ------- @@ -332,12 +342,6 @@ def unit(self) -> str: """ set units for this Condition subobject - Examples - -------- - ```python - my_condition.unit = "gram" - ``` - Returns ------- unit: str @@ -351,12 +355,6 @@ def uncertainty(self) -> Optional[Union[Number, str]]: """ set uncertainty value for this Condition subobject - Examples - -------- - ```python - my_condition.uncertainty = "0.1" - ``` - Returns ------- uncertainty: Union[Number, None] @@ -378,9 +376,14 @@ def set_uncertainty(self, new_uncertainty: Union[Number, str, None], new_uncerta Examples -------- - ```python - my_condition.set_uncertainty(new_uncertainty="0.2", new_uncertainty_type="std") - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.set_uncertainty(new_uncertainty=0.2, new_uncertainty_type="stdev") Returns ------- @@ -397,12 +400,6 @@ def uncertainty_type(self) -> str: [Uncertainty type](https://app.criptapp.org/vocab/uncertainty_type) must come from CRIPT controlled vocabulary - Examples - -------- - ```python - my_condition.uncertainty_type = "std" - ``` - Returns ------- uncertainty_type: str @@ -418,9 +415,14 @@ def set_id(self) -> Union[int, None]: Examples -------- - ```python - my_condition.set_id = 0 - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.set_id = 0 Returns ------- @@ -455,9 +457,14 @@ def measurement_id(self) -> Union[int, None]: Examples -------- - ```python - my_condition.measurement_id = 0 - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.measurement_id = 0 Returns ------- @@ -472,6 +479,17 @@ def measurement_id(self, new_measurement_id: Union[int, None]) -> None: """ set the set_id for this Condition subobject + Examples + -------- + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_condition.measurement_id = 1 + Parameters ---------- new_measurement_id : Union[int, None] @@ -492,24 +510,25 @@ def data(self) -> List[Data]: Examples -------- - ```python - # create file nodes for the data node - my_file = cript.File( - source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", - type="calibration", - extension=".pdf", - ) - - # create data node and add the file node to it - my_data = cript.Data( - name="my data node name", - type="afm_amp", - file=my_file, - ) - - # add data node to Condition subobject - my_condition.data = [my_data] - ``` + >>> import cript + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", + ... type="calibration", + ... extension=".pdf", + ... ) + >>> my_data = cript.Data( + ... name="my data node name", + ... type="afm_amp", + ... file=[my_file], + ... ) + >>> my_condition.data = [my_data] Returns ------- diff --git a/src/cript/nodes/subobjects/equipment.py b/src/cript/nodes/subobjects/equipment.py index 625ae2648..ec6f6c7eb 100644 --- a/src/cript/nodes/subobjects/equipment.py +++ b/src/cript/nodes/subobjects/equipment.py @@ -32,9 +32,9 @@ class Equipment(UUIDBaseNode): |-------------|-----------------|-----------------------------------------------|--------------------------------------------------------------------------------|----------|-------| | key | str | hot plate | material | True | True | | description | str | Hot plate with silicon oil bath with stir bar | additional details about the equipment | | | - | condition | list[Condition] | | conditions under which the property was measured | | | + | condition | list[Condition] | | conditions under which the property was measured | | | | files | list[File] | | list of file nodes to link to calibration or equipment specification documents | | | - | citation | list[Citation] | | reference to a book, paper, or scholarly work | | | + | citation | list[Citation] | | reference to a book, paper, or scholarly work | | | ## JSON Representation ```json @@ -67,7 +67,7 @@ def __init__(self, key: str, description: str = "", condition: Union[List[Condit Parameters ---------- key : str - Equipment key must come from [CRIPT Controlled Vocabulary]() + Equipment key must come from [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/equipment_key) description : str, optional additional details about the equipment, by default "" condition : Union[List[Condition], None], optional @@ -77,11 +77,10 @@ def __init__(self, key: str, description: str = "", condition: Union[List[Condit citation : Union[List[Citation], None], optional reference to a scholarly work, by default None - Example - ------- - ```python - my_equipment = cript.Equipment(key="burner") - ``` + Examples + -------- + >>> import cript + >>> my_equipment = cript.Equipment(key="burner") Returns ------- @@ -108,14 +107,13 @@ def key(self) -> str: Examples -------- - ```python - my_equipment = cript.Equipment(key="burner") - ``` + >>> import cript + >>> my_equipment = cript.Equipment(key="burner") + >>> my_equipment.key = "hot_plate" Returns ------- Equipment: str - """ return self._json_attrs.key @@ -125,8 +123,6 @@ def key(self, new_key: str) -> None: """ set the equipment key - > Equipment key must come from [CRIPT Controlled Vocabulary]() - Parameters ---------- new_key : str @@ -147,9 +143,9 @@ def description(self) -> str: Examples -------- - ```python - my_equipment.description = "additional details about the equipment" - ``` + >>> import cript + >>> my_equipment = cript.Equipment(key="burner") + >>> my_equipment.description = "additional details about the equipment" Returns ------- @@ -184,18 +180,15 @@ def condition(self) -> List[Condition]: Examples -------- - ```python - # create a Condition sub-object - my_condition = cript.Condition( - key="temperature", - type="value", - value=22, - unit="C", - ) - - # add Condition sub-object to Equipment sub-object - my_equipment.condition = [my_condition] - ``` + >>> import cript + >>> my_equipment = cript.Equipment(key="burner") + >>> my_condition = cript.Condition( + ... key="temperature", + ... type="value", + ... value=22, + ... unit="C", + ... ) + >>> my_equipment.condition = [my_condition] Returns ------- @@ -230,18 +223,15 @@ def file(self) -> List[File]: Examples -------- - ```python - # create a file node to be added to the equipment sub-object - my_file = cript.File( - source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", - type="calibration", - extension=".pdf", - ) - - # add file node to equipment sub-object - my_equipment.file = [my_file] - - ``` + >>> import cript + >>> my_equipment = cript.Equipment(key="burner") + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", + ... type="calibration", + ... extension=".pdf", + ... ) + >>> my_equipment.file = [my_file] Returns ------- @@ -276,30 +266,26 @@ def citation(self) -> List[Citation]: Examples -------- - ```python - # create reference node for the citation node - title = "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " - title += "SOft coarse grained Monte-Carlo Acceleration (SOMA)" - - my_reference = cript.Reference( - type="journal_article", - title=title, - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - # create citation node and add reference node to it - my_citation = cript.Citation(type="reference", reference=my_reference) - - # add citation subobject to equipment - my_equipment.citation = [my_citation] - ``` + >>> import cript + >>> my_equipment = cript.Equipment(key="burner") + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... type="journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + >>> my_equipment.citation = [my_citation] Returns ------- diff --git a/src/cript/nodes/subobjects/ingredient.py b/src/cript/nodes/subobjects/ingredient.py index b4f12020e..a6ec82aa9 100644 --- a/src/cript/nodes/subobjects/ingredient.py +++ b/src/cript/nodes/subobjects/ingredient.py @@ -78,19 +78,15 @@ def __init__(self, material: Material, quantity: List[Quantity], keyword: Option Examples -------- - ```python - import cript - - # create material and identifier for the ingredient sub-object - my_identifier = [{"bigsmiles": "123456"}] - my_material = cript.Material(name="my material", identifier=my_identifier) - - # create quantity sub-object - my_quantity = cript.Quantity(key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev") - - # create ingredient sub-object and add all appropriate nodes/sub-objects - my_ingredient = cript.Ingredient(material=my_material, quantity=my_quantity, keyword="catalyst") - ``` + >>> import cript + >>> my_identifier = [{"bigsmiles": "123456"}] + >>> my_material = cript.Material(name="my material", identifier=my_identifier) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, quantity=[my_quantity], keyword=["catalyst"] + ... ) Parameters ---------- @@ -99,7 +95,8 @@ def __init__(self, material: Material, quantity: List[Quantity], keyword: Option quantity : List[Quantity] list of quantity sub-objects keyword : List[str], optional - ingredient keyword must come from [CRIPT Controlled Vocabulary](), by default "" + ingredient keyword must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/ingredient_keyword), by default "" Returns ------- @@ -152,18 +149,21 @@ def set_material(self, new_material: Material, new_quantity: List[Quantity]) -> Examples -------- - ```python - my_identifier = [{"bigsmiles": "123456"}] - my_new_material = cript.Material(name="my material", identifier=my_identifier) - - my_new_quantity = cript.Quantity( - key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" - ) - - # set new material and list of quantities - my_ingredient.set_material(new_material=my_new_material, new_quantity=[my_new_quantity]) - - ``` + >>> import cript + >>> my_material = cript.Material(name="my material", identifier=[{"bigsmiles": "123456"}]) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, quantity=[my_quantity], keyword=["catalyst"] + ... ) + >>> my_new_material = cript.Material( + ... name="my material", identifier=[{"bigsmiles": "78910"}] + ... ) + >>> my_new_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient.set_material(new_material=my_new_material, new_quantity=[my_new_quantity]) Parameters ---------- @@ -188,10 +188,15 @@ def keyword(self) -> List[str]: Examples -------- - ```python - # set new ingredient keyword - my_ingredient.keyword = "computation" - ``` + >>> import cript + >>> my_material = cript.Material(name="my material", identifier=[{"bigsmiles": "123456"}]) + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_ingredient = cript.Ingredient( + ... material=my_material, quantity=[my_quantity], keyword=["catalyst"] + ... ) + >>> my_ingredient.keyword = ["computation"] Returns ------- @@ -206,8 +211,6 @@ def keyword(self, new_keyword: List[str]) -> None: """ set new ingredient keyword to replace the current - ingredient keyword must come from the [CRIPT controlled vocabulary]() - Parameters ---------- new_keyword : str diff --git a/src/cript/nodes/subobjects/parameter.py b/src/cript/nodes/subobjects/parameter.py index 55726e7fd..68031cb79 100644 --- a/src/cript/nodes/subobjects/parameter.py +++ b/src/cript/nodes/subobjects/parameter.py @@ -75,7 +75,7 @@ def __init__(self, key: str, value: Number, unit: Optional[str] = None, **kwargs Parameters ---------- key : str - Parameter key must come from [CRIPT Controlled Vocabulary]() + Parameter key must come from [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/parameter_key) value : Union[int, float] Parameter value unit : Union[str, None], optional @@ -83,11 +83,8 @@ def __init__(self, key: str, value: Number, unit: Optional[str] = None, **kwargs Examples -------- - ```python - import cript - - my_parameter = cript.Parameter("update_frequency", 1000.0, "1/second") - ``` + >>> import cript + >>> my_parameter = cript.Parameter("update_frequency", 1000.0, "1/second") Returns ------- @@ -115,9 +112,9 @@ def key(self) -> str: Examples -------- - ```python - my_parameter.key = "bond_type" - ``` + >>> import cript + >>> my_parameter = cript.Parameter("update_frequency", 1000.0, "1/second") + >>> my_parameter.key = "damping_time" Returns ------- @@ -132,8 +129,6 @@ def key(self, new_key: str) -> None: """ set new key for the Parameter sub-object - Parameter key must come from [CRIPT Controlled Vocabulary]() - Parameters ---------- new_key : str @@ -154,9 +149,9 @@ def value(self) -> Optional[Number]: Examples -------- - ```python - my_parameter.value = 1 - ``` + >>> import cript + >>> my_parameter = cript.Parameter("update_frequency", 1000.0, "1/second") + >>> my_parameter.value = 1 Returns ------- @@ -191,9 +186,9 @@ def unit(self) -> Union[str, None]: Examples -------- - ```python - my_parameter.unit = "gram" - ``` + >>> import cript + >>> my_parameter = cript.Parameter("update_frequency", 1000.0, "1/second") + >>> my_parameter.unit = "gram" Returns ------- diff --git a/src/cript/nodes/subobjects/property.py b/src/cript/nodes/subobjects/property.py index e112a22f3..831cfc5df 100644 --- a/src/cript/nodes/subobjects/property.py +++ b/src/cript/nodes/subobjects/property.py @@ -16,8 +16,9 @@ class Property(UUIDBaseNode): """ ## Definition - [Property](https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf#page=18) sub-objects - are qualities/traits of a [material](../../primary_nodes/material) or or [Process](../../primary_nodes/process) + [Property](https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf#page=18) + sub-objects are qualities/traits of a [material](../../primary_nodes/material) or + [Process](../../primary_nodes/process) --- @@ -34,23 +35,23 @@ class Property(UUIDBaseNode): ## Attributes - | attribute | type | example | description | required | vocab | - |--------------------|-------------------|------------------------------------------------------------------------|------------------------------------------------------------------------------|----------|-------| - | key | str | modulus_shear | type of property | True | True | - | type | str | min | type of value stored | True | True | - | value | Any | 1.23 | value or quantity | True | | - | unit | str | gram | unit for value | True | | - | uncertainty | Number | 0.1 | uncertainty of value | | | - | uncertainty_type | str | standard_deviation | type of uncertainty | | True | - | component | list[Material] | | material that the property relates to** | | | - | structure | str | {\[\]\[$\]\[C:1\]\[C:1\]\[$\], \[$\]\[C:2\]\[C:2\](\[C:2\]) \[$\]\[\]} | specific chemical structure associate with the property with atom mappings** | | | - | method | str | sec | approach or source of property data | | True | - | sample_preparation | Process | | sample preparation | | | - | condition | list[Condition] | | conditions under which the property was measured | | | - | data | Data | | data node | | | - | computation | list[Computation] | | computation method that produced property | | | - | citation | list[Citation] | | reference to a book, paper, or scholarly work | | | - | notes | str | | miscellaneous information, or custom data structure (e.g.; JSON) | | | + | attribute | type | example | description | required | vocab | + |--------------------|-------------------|-----------------------------------------|------------------------------------------------------------------------------|----------|-------| + | key | str | modulus_shear | type of property | True | True | + | type | str | min | type of value stored | True | True | + | value | Any | 1.23 | value or quantity | True | | + | unit | str | gram | unit for value | True | | + | uncertainty | Number | 0.1 | uncertainty of value | | | + | uncertainty_type | str | standard_deviation | type of uncertainty | | True | + | component | list[Material] | | material that the property relates to** | | | + | structure | str | {\\[\\]\\[$\\]\\[C:1\\]\\[C:1\\]\\[$\\] | specific chemical structure associate with the property with atom mappings** | | | + | method | str | sec | approach or source of property data | | True | + | sample_preparation | Process | | sample preparation | | | + | condition | list[Condition] | | conditions under which the property was measured | | | + | data | Data | | data node | | | + | computation | list[Computation] | | computation method that produced property | | | + | citation | list[Citation] | | reference to a book, paper, or scholarly work | | | + | notes | str | | miscellaneous information, or custom data structure (e.g.; JSON) | | | ## JSON Representation @@ -113,9 +114,9 @@ def __init__( Parameters ---------- key : str - type of property, Property key must come from the [CRIPT Controlled Vocabulary]() + type of property, Property key must come from the CRIPT Controlled Vocabulary type : str - type of value stored, Property type must come from the [CRIPT Controlled Vocabulary]() + type of value stored, Property type must come from the CRIPT Controlled Vocabulary value : Union[Number, None] value or quantity unit : str @@ -146,11 +147,8 @@ def __init__( Examples -------- - ```python - import cript - - my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") Returns ------- @@ -197,9 +195,9 @@ def key(self) -> str: Examples -------- - ```python - my_parameter.key = "angle_rdist" - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.key = "angle_rdist" Returns ------- @@ -235,9 +233,10 @@ def type(self) -> str: [property type](https://app.criptapp.org/vocab/) must come from CRIPT controlled vocabulary Examples - ```python - my_property.type = "max" - ``` + --------- + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.type = "max" Returns ------- @@ -284,9 +283,9 @@ def set_value(self, new_value: Union[Number, str, None], new_unit: str) -> None: Examples --------- - ```python - my_property.set_value(new_value=1, new_unit="gram") - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.set_value(new_value=1, new_unit="gram") Parameters ---------- @@ -344,9 +343,9 @@ def set_uncertainty(self, new_uncertainty: Optional[Number], new_uncertainty_typ Examples -------- - ```python - my_property.set_uncertainty(new_uncertainty=2, new_uncertainty_type="fwhm") - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.set_uncertainty(new_uncertainty=2, new_uncertainty_type="fwhm") Returns ------- @@ -379,14 +378,10 @@ def component(self) -> List[Material]: Examples --------- - ```python - - my_identifier = [{"bigsmiles": "123456"}] - my_material = cript.Material(name="my material", identifier=my_identifier) - - # add material node as component to Property subobject - my_property.component = my_material - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_material = cript.Material(name="my material", identifier=[{"bigsmiles": "123456"}]) + >>> my_property.component = [my_material] Returns ------- @@ -421,9 +416,9 @@ def structure(self) -> str: Examples -------- - ```python - my_property.structure = "{[][$][C:1][C:1][$],[$][C:2][C:2]([C:2])[$][]}" - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.structure = "{[][$][C:1][C:1][$],[$][C:2][C:2]([C:2])[$][]}" Returns ------- @@ -460,9 +455,9 @@ def method(self) -> str: Examples -------- - ```python - my_property.method = "ASTM_D3574_Test_A" - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.method = "ASTM_D3574_Test_A" Returns ------- @@ -477,8 +472,6 @@ def method(self, new_method: str) -> None: """ set the Property method - Property method must come from [CRIPT Controlled Vocabulary]() - Parameters ---------- new_method : str @@ -499,11 +492,10 @@ def sample_preparation(self) -> Union[Process, None]: Examples -------- - ```python - my_process = cript.Process(name="my process name", type="affinity_pure") - - my_property.sample_preparation = my_process - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_process = cript.Process(name="my process name", type="affinity_pure") + >>> my_property.sample_preparation = my_process Returns ------- @@ -538,11 +530,10 @@ def condition(self) -> List[Condition]: Examples -------- - ```python - my_condition = cript.Condition(key="atm", type="max", value=1) - - my_property.condition = [my_condition] - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_condition = cript.Condition(key="atm", type="max", value=1) + >>> my_property.condition = [my_condition] Returns ------- @@ -577,21 +568,17 @@ def data(self) -> List[Data]: Examples -------- - ```python - # create file node for the Data node - my_file = cript.File( - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary", - ) - - # create data node for the property subobject - my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) - - # add data node to Property subobject - my_property.data = my_data - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_file = cript.File( + ... name="my file node name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary", + ... ) + >>> my_data = cript.Data(name="my data name", type="afm_amp", file=[my_file]) + >>> my_property.data = [my_data] Returns ------- @@ -626,11 +613,10 @@ def computation(self) -> List[Computation]: Examples -------- - ```python - my_computation = cript.Computation(name="my computation name", type="analysis") - - my_property.computation = [my_computation] - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_computation = cript.Computation(name="my computation name", type="analysis") + >>> my_property.computation = [my_computation] Returns ------- @@ -665,30 +651,26 @@ def citation(self) -> List[Citation]: Examples -------- - ```python - # create reference node for the citation node - title = "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " - title += "Soft coarse grained Monte-Carlo Acceleration (SOMA)" - - my_reference = cript.Reference( - type="journal_article", - title=title, - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - # create citation node and add reference node to it - my_citation = cript.Citation(type="reference", reference=my_reference) - - # add citation to Property subobject - my_property.citation = [my_citation] - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... type="journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = cript.Citation(type="reference", reference=my_reference) + >>> my_property.citation = [my_citation] Returns ------- @@ -723,9 +705,9 @@ def notes(self) -> str: Examples -------- - ```python - my_property.notes = "these are my notes" - ``` + >>> import cript + >>> my_property = cript.Property(key="air_flow", type="min", value=1.00, unit="gram") + >>> my_property.notes = "these are my notes" Returns ------- @@ -738,7 +720,7 @@ def notes(self) -> str: @beartype def notes(self, new_notes: str) -> None: """ - set the notes for this Property subobject + set the notes for this Property sub-object Parameters ---------- diff --git a/src/cript/nodes/subobjects/quantity.py b/src/cript/nodes/subobjects/quantity.py index 1d9613f9d..392bed68f 100644 --- a/src/cript/nodes/subobjects/quantity.py +++ b/src/cript/nodes/subobjects/quantity.py @@ -69,7 +69,8 @@ def __init__(self, key: str, value: Number, unit: str, uncertainty: Optional[Num Parameters ---------- key : str - type of quantity. Quantity key must come from [CRIPT Controlled Vocabulary]() + type of quantity. Quantity key must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/quantity_key) value : Number amount of material unit : str @@ -77,17 +78,15 @@ def __init__(self, key: str, value: Number, unit: str, uncertainty: Optional[Num uncertainty : Union[Number, None], optional uncertainty of value, by default None uncertainty_type : str, optional - type of uncertainty. Quantity uncertainty type must come from [CRIPT Controlled Vocabulary](), by default "" + type of uncertainty. Quantity uncertainty type must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/uncertainty_type), by default "" Examples -------- - ```python - import cript - - my_quantity = cript.Quantity( - key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" - ) - ``` + >>> import cript + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) Returns ------- @@ -113,18 +112,21 @@ def set_key_unit(self, new_key: str, new_unit: str) -> None: """ set the Quantity key and unit attributes - Quantity key must come from [CRIPT Controlled Vocabulary]() + Quantity key must come from [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/quantity_key) Examples -------- - ```python - my_quantity.set_key_unit(new_key="mass", new_unit="gram") - ``` + >>> import cript + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_quantity.set_key_unit(new_key="mass", new_unit="kg") Parameters ---------- new_key : str - new Quantity key. Quantity key must come from [CRIPT Controlled Vocabulary]() + new Quantity key. Quantity key must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/quantity_key) new_unit : str new unit @@ -158,9 +160,11 @@ def value(self) -> Union[int, float, str]: Examples -------- - ```python - my_quantity.value = 1 - ``` + >>> import cript + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) + >>> my_quantity.value = 1 Returns ------- @@ -235,20 +239,23 @@ def set_uncertainty(self, uncertainty: Optional[Number], type: str) -> None: Uncertainty and uncertainty type are set at the same time to keep the value and type in sync - `uncertainty_type` must come from [CRIPT Controlled Vocabulary]() + `uncertainty_type` must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/uncertainty_type) Examples -------- - ```python - my_property.set_uncertainty(uncertainty=1, type="stderr") - ``` + >>> import cript + >>> my_quantity = cript.Quantity( + ... key="mass", value=11.2, unit="kg", uncertainty=0.2, uncertainty_type="stdev" + ... ) Parameters ---------- uncertainty : Number uncertainty value type : str - type of uncertainty, uncertainty_type must come from [CRIPT Controlled Vocabulary]() + type of uncertainty, uncertainty_type must come from + [CRIPT Controlled Vocabulary](https://app.criptapp.org/vocab/uncertainty_type) Returns ------- diff --git a/src/cript/nodes/subobjects/software.py b/src/cript/nodes/subobjects/software.py index 4ee60ad35..e96ff60c2 100644 --- a/src/cript/nodes/subobjects/software.py +++ b/src/cript/nodes/subobjects/software.py @@ -70,11 +70,10 @@ def __init__(self, name: str, version: str, source: str = "", **kwargs): Examples -------- - ```python - my_software = cript.Software( - name="my software name", version="v1.0.0", source="https://myurl.com" - ) - ``` + >>> import cript + >>> my_software = cript.Software( + ... name="my software name", version="v1.0.0", source="https://myurl.com" + ... ) Returns ------- @@ -94,9 +93,11 @@ def name(self) -> str: Examples -------- - ```python - my_software.name = "my software name" - ``` + >>> import cript + >>> my_software = cript.Software( + ... name="my software name", version="v1.0.0", source="https://myurl.com" + ... ) + >>> my_software.name = "my software name" Returns ------- @@ -129,7 +130,13 @@ def version(self) -> str: """ Software version - my_software.version = "1.2.3" + Examples + -------- + >>> import cript + >>> my_software = cript.Software( + ... name="my software name", version="v1.0.0", source="https://myurl.com" + ... ) + >>> my_software.version = "1.2.3" Returns ------- @@ -164,9 +171,11 @@ def source(self) -> str: Examples -------- - ```python - my_software.source = "https://mywebsite.com" - ``` + >>> import cript + >>> my_software = cript.Software( + ... name="my software name", version="v1.0.0", source="https://myurl.com" + ... ) + >>> my_software.source = "https://myNewWebsite.com" Returns ------- diff --git a/src/cript/nodes/subobjects/software_configuration.py b/src/cript/nodes/subobjects/software_configuration.py index 8e727f83a..8fba5a945 100644 --- a/src/cript/nodes/subobjects/software_configuration.py +++ b/src/cript/nodes/subobjects/software_configuration.py @@ -83,13 +83,9 @@ def __init__(self, software: Software, algorithm: Optional[List[Algorithm]] = No Examples --------- - ```python - import cript - - my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") - - my_software_configuration = cript.SoftwareConfiguration(software=my_software) - ``` + >>> import cript + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) Returns ------- @@ -112,13 +108,10 @@ def software(self) -> Union[Software, None]: Examples -------- - ```python - my_software = cript.Software( - name="my software name", version="v1.0.0", source="https://myurl.com" - ) - - my_software_configuration.software = my_software - ``` + >>> import cript + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) + >>> my_software_configuration.software = my_software Returns ------- @@ -153,11 +146,11 @@ def algorithm(self) -> List[Algorithm]: Examples -------- - ```python - my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") - - my_software_configuration.algorithm = [my_algorithm] - ``` + >>> import cript + >>> my_algorithm = cript.Algorithm(key="mc_barostat", type="barostat") + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) + >>> my_software_configuration.algorithm = [my_algorithm] Returns ------- @@ -197,10 +190,12 @@ def notes(self) -> str: my_software_configuration.notes = "these are my awesome notes!" ``` - ### JSON Notes - ```python - my_software_configuration.notes = "{'notes subject': 'notes contents'}" - ``` + Examples + ------- + >>> import cript + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) + >>> my_software_configuration.notes = "{'notes subject': 'notes contents'}" Returns ------- @@ -235,30 +230,27 @@ def citation(self) -> List[Citation]: Examples -------- - ```python - title = "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " - title += "SOft coarse grained Monte-Carlo Acceleration (SOMA)" - - # create reference node - my_reference = cript.Reference( - type"journal_article", - title=title, - author=["Ludwig Schneider", "Marcus Müller"], - journal="Computer Physics Communications", - publisher="Elsevier", - year=2019, - pages=[463, 476], - doi="10.1016/j.cpc.2018.08.011", - issn="0010-4655", - website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", - ) - - # create citation sub-object and add reference to it - my_citation = Citation("reference", my_reference) - - # add citation to algorithm node - my_software_configuration.citation = [my_citation] - ``` + >>> import cript + >>> title = ( + ... "Multi-architecture Monte-Carlo (MC) simulation of soft coarse-grained polymeric materials: " + ... "Soft coarse grained Monte-Carlo Acceleration (SOMA)" + ... ) + >>> my_reference = cript.Reference( + ... type="journal_article", + ... title=title, + ... author=["Ludwig Schneider", "Marcus Müller"], + ... journal="Computer Physics Communications", + ... publisher="Elsevier", + ... year=2019, + ... pages=[463, 476], + ... doi="10.1016/j.cpc.2018.08.011", + ... issn="0010-4655", + ... website="https://www.sciencedirect.com/science/article/pii/S0010465518303072", + ... ) + >>> my_citation = Citation("reference", my_reference) + >>> my_software = cript.Software(name="LAMMPS", version="23Jun22", source="lammps.org") + >>> my_software_configuration = cript.SoftwareConfiguration(software=my_software) + >>> my_software_configuration.citation = [my_citation] Returns ------- diff --git a/src/cript/nodes/supporting_nodes/file.py b/src/cript/nodes/supporting_nodes/file.py index 28601dde4..c3e79b458 100644 --- a/src/cript/nodes/supporting_nodes/file.py +++ b/src/cript/nodes/supporting_nodes/file.py @@ -111,7 +111,6 @@ class File(PrimaryBaseNode): "data_dictionary": "my file's data dictionary", } ``` - """ @dataclass(frozen=True) @@ -128,7 +127,7 @@ class JsonAttributes(PrimaryBaseNode.JsonAttributes): _json_attrs: JsonAttributes = JsonAttributes() @beartype - def __init__(self, name: str, source: str, type: str, extension: str = "", data_dictionary: str = "", notes: str = "", **kwargs): + def __init__(self, name: str, source: str, type: str, extension: str, data_dictionary: str = "", notes: str = "", **kwargs): """ create a File node @@ -139,7 +138,8 @@ def __init__(self, name: str, source: str, type: str, extension: str = "", data_ source: str link or path to local file type: str - Pick a file type from CRIPT controlled vocabulary [File types]() + Pick a file type from CRIPT controlled vocabulary + [File types](https://app.criptapp.org/vocab/file_type) extension:str file extension data_dictionary:str @@ -152,33 +152,31 @@ def __init__(self, name: str, source: str, type: str, extension: str = "", data_ Examples -------- - ### Minimal File Node - ```python - my_file = cript.File( - name="my file name", - source="https://criptapp.org", - type="calibration", - ) - ``` - - ### Maximal File Node - ```python - my_file = cript.File( - name="my file name", - source="https://criptapp.org", - type="calibration", - extension=".csv", - data_dictionary="my file's data dictionary", - notes="my notes for this file" - ) - ``` + ### Web URL File Node + >>> import cript + >>> my_file = cript.File( + ... name="my file name", + ... source="https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf", + ... type="calibration", + ... extension=".pdf", + ... data_dictionary="my file's data dictionary", + ... notes="my notes for this file", + ... ) + + ### Local Source File Node + >>> import cript + >>> my_file = cript.File( + ... name="my file name", + ... source="/home/user/MIT/project/my_file.csv", + ... type="calibration", + ... extension=".csv", + ... data_dictionary="my file's data dictionary", + ... notes="my notes for this file", + ... ) """ super().__init__(name=name, notes=notes, **kwargs) - # TODO check if vocabulary is valid or not - # is_vocab_valid("file type", type) - # setting every attribute except for source, which will be handled via setter self._json_attrs = replace( self._json_attrs, @@ -198,22 +196,23 @@ def ensure_uploaded(self, api=None): It is not necessary to call this function manually. A saved project automatically ensures uploaded files, it is recommend to rely on the automatic upload. - Parameters: + Parameters ----------- - api: cript.API, optional API object that performs the upload. If None, the globally cached object is being used. Examples -------- - ### Example Minimal File Node - ```python - my_file = cript.File(source="/local/path/to/file", type="calibration") - my_file.ensure_uploaded() - my_file.source # Starts with http now - ``` - + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="/local/path/to/file", + ... type="calibration", + ... extension="csv", + ... ) + >>> my_file.ensure_uploaded() # doctest: +SKIP + >>> my_file.source # changed to cloud storage object name # doctest: +SKIP """ if _is_local_file(file_source=self.source): @@ -230,16 +229,17 @@ def source(self) -> str: The File node source can be set to be either a path to a local file on disk or a URL path to a file on the web. - Example + Examples -------- URL File Source - ```python - my_file.source = "https://pubs.acs.org/doi/suppl/10.1021/acscentsci.3c00011/suppl_file/oc3c00011_si_001.pdf" - ``` - Local File Path - ```python - my_file.source = "/home/user/project/my_file.csv" - ``` + >>> import cript + >>> my_file = cript.File( + ... name="my file name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... ) + >>> my_file.source = "/home/user/project/my_file.csv" Returns ------- @@ -268,11 +268,10 @@ def source(self, new_source: str) -> None: ---------- new_source: str - Example - ------- - ```python - my_file.source = "https://pubs.acs.org/doi/10.1021/acscentsci.3c00011" - ``` + Examples + -------- + >>> import cript + >>> my_file.source = "https://pubs.acs.org/doi/10.1021/acscentsci.3c00011" Returns ------- @@ -287,16 +286,21 @@ def type(self) -> str: """ The [File type](https://app.criptapp.org/vocab/file_type) must come from CRIPT controlled vocabulary - Example - ------- - ```python - my_file.type = "calibration" - ``` + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".csv", + ... ) + >>> my_file.type = "calibration" Returns ------- file type: str - file type must come from [CRIPT controlled vocabulary]() + file type must come from [CRIPT controlled vocabulary](https://app.criptapp.org/vocab/file_type) """ return self._json_attrs.type @@ -312,11 +316,10 @@ def type(self, new_type: str) -> None: ----------- new_type: str - Example - ------- - ```python - my_file.type = "computation_config" - ``` + Examples + -------- + >>> import cript + >>> my_file.type = "computation_config" Returns ------- @@ -333,11 +336,19 @@ def extension(self) -> str: """ The file extension property explicitly states what is the file extension of the file node. - Example - ------- - ```python - my_file_node.extension = ".csv"` - ``` + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".text", + ... ) + >>> my_file.extension = ".csv" + + !!! Note "file extensions must start with a dot" + File extensions must start with a dot, for example `.csv` or `.pdf` Returns ------- @@ -357,11 +368,10 @@ def extension(self, new_extension) -> None: new_extension: str new file extension to overwrite the current file extension - Example - ------- - ```python - my_file.extension = ".pdf" - ``` + Examples + -------- + >>> import cript + >>> my_file.extension = ".pdf" Returns ------- @@ -383,10 +393,15 @@ def data_dictionary(self) -> str: It is advised for this field to be written in JSON format Examples - ------- - ```python - my_file.data_dictionary = "{'notes': 'This is something that describes my file node.'}" - ``` + --------- + >>> import cript + >>> my_file = cript.File( + ... name="my file name", + ... source="https://criptapp.org", + ... type="calibration", + ... extension=".png", + ... ) + >>> my_file.data_dictionary = "{'notes': 'This is something that describes my file node.'}" Returns ------- @@ -422,6 +437,19 @@ def download( download this file to current working directory or a specific destination. The file name will come from the file_node.name and the extension will come from file_node.extension + Examples + -------- + >>> import cript + >>> my_file = cript.File( + ... name="my file node name", + ... source="/local/path/to/file", + ... type="calibration", + ... extension=".jpeg", + ... ) + >>> my_file.ensure_uploaded() # doctest: +SKIP + >>> my_file.source # changed to cloud storage object name # doctest: +SKIP + >>> my_file.download() # doctest: +SKIP + Notes ----- Whether the file extension is written like `.csv` or `csv` the program will work correctly diff --git a/src/cript/nodes/util/__init__.py b/src/cript/nodes/util/__init__.py index a26def4b8..b56af44ea 100644 --- a/src/cript/nodes/util/__init__.py +++ b/src/cript/nodes/util/__init__.py @@ -394,33 +394,30 @@ def _is_node_field_valid(node_type_list: list) -> bool: return False -def load_nodes_from_json(nodes_json: str): +def load_nodes_from_json(nodes_json: Union[str, Dict]): """ User facing function, that return a node and all its children from a json string input. Parameters ---------- - nodes_json: str + nodes_json: Union[str, dict] JSON string representation of a CRIPT node Examples -------- - ```python - # Get updated project from API - my_paginator = api.search( - node_type=cript.Project, - search_mode=cript.SearchModes.EXACT_NAME, - value_to_search="my project name", - ) - - # Take specific Project you want from paginator - my_project_from_api_dict: dict = my_paginator.current_page_results[0] - - # Deserialize your Project dict into a Project node - my_project_node_from_api = cript.load_nodes_from_json( - nodes_json=json.dumps(my_project_from_api_dict) - ) - ``` + >>> import cript + >>> # Get updated project from API + >>> my_paginator = api.search( + ... node_type=cript.Project, + ... search_mode=cript.SearchModes.EXACT_NAME, + ... value_to_search="my project name", + ... ) # doctest: +SKIP + >>> # Take specific Project you want from paginator + >>> my_project_from_api_dict: dict = my_paginator.current_page_results[0] # doctest: +SKIP + >>> # Deserialize your Project dict into a Project node + >>> my_project_node_from_api = cript.load_nodes_from_json( # doctest: +SKIP + ... nodes_json=my_project_from_api_dict + ... ) Raises ------ @@ -439,16 +436,28 @@ def load_nodes_from_json(nodes_json: str): The function is intended for deserializing CRIPT nodes and should not be used for generic JSON. - Returns ------- Union[CRIPT Node, List[CRIPT Node]] Typically returns a single CRIPT node, but if given a list of nodes, then it will serialize them and return a list of CRIPT nodes """ + # Initialize the custom decoder hook for JSON deserialization node_json_hook = _NodeDecoderHook() - json_nodes = json.loads(nodes_json, object_hook=node_json_hook) + # Check if the input is already a Python dictionary + if isinstance(nodes_json, Dict): + # If it's a dictionary, directly use the decoder hook to deserialize it + return node_json_hook(nodes_json) + + # Check if the input is a JSON-formatted string + elif isinstance(nodes_json, str): + # If it's a JSON string, parse and deserialize it using the decoder hook + return json.loads(nodes_json, object_hook=node_json_hook) + + # Raise an error if the input type is unsupported + else: + raise TypeError(f"Unsupported type for nodes_json: {type(nodes_json)}") # TODO: enable this logic to replace proxies, once beartype is OK with that. # def recursive_proxy_replacement(node, handled_nodes): # if isinstance(node, _UIDProxy): @@ -473,7 +482,6 @@ def load_nodes_from_json(nodes_json: str): # return node # handled_nodes = set() # recursive_proxy_replacement(json_nodes, handled_nodes) - return json_nodes def add_orphaned_nodes_to_project(project: Project, active_experiment: Experiment, max_iteration: int = -1): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/api/test_api.py b/tests/api/test_api.py index 7dd301311..0a34d6179 100644 --- a/tests/api/test_api.py +++ b/tests/api/test_api.py @@ -8,9 +8,9 @@ import pytest import requests -from conftest import HAS_INTEGRATION_TESTS_ENABLED import cript +from conftest import HAS_INTEGRATION_TESTS_ENABLED from cript.api.exceptions import InvalidVocabulary from cript.api.paginator import Paginator from cript.nodes.exceptions import CRIPTNodeSchemaError @@ -155,6 +155,40 @@ def test_is_node_schema_valid(cript_api: cript.API) -> None: assert cript_api._is_node_schema_valid(node_json=json.dumps(valid_file_dict), is_patch=False) is True +def test_is_node_schema_valid_skipped(cript_api: cript.API) -> None: + """ + test that a CRIPT node can be correctly validated and invalidated with the db schema, when skipping tests is active + + * test db schema validation with an invalid node, and it should be invalid, but only detected if forced + + Notes + ----- + * does not test if serialization/deserialization works correctly, + just tests if the node schema can work correctly if serialization was correct + + """ + + def extract_base_url(url): + # Split the URL by "//" first to separate the scheme (like http, https) + parts = url.split("//", 1) + scheme, rest = parts if len(parts) > 1 else ("", parts[0]) + + # Split the rest by the first "/" to separate the domain + domain = rest.split("/", 1)[0] + return f"{scheme}//{domain}" if scheme else domain + + with cript.API(host=extract_base_url(cript_api.host), api_token=cript_api._api_token, storage_token=cript_api._storage_token) as local_cript_api: + local_cript_api.skip_validation = True + # ------ invalid node schema------ + invalid_schema = {"invalid key": "invalid value", "node": ["Material"]} + + # Test should be skipped + assert local_cript_api._is_node_schema_valid(node_json=json.dumps(invalid_schema), is_patch=False) is None + + with pytest.raises(CRIPTNodeSchemaError): + local_cript_api._is_node_schema_valid(node_json=json.dumps(invalid_schema), is_patch=False, force_validation=True) + + def test_get_vocabulary_by_category(cript_api: cript.API) -> None: """ tests if a vocabulary can be retrieved by category @@ -356,7 +390,7 @@ def test_api_search_exact_name(cript_api: cript.API) -> None: @pytest.mark.skipif(not HAS_INTEGRATION_TESTS_ENABLED, reason="requires a real cript_api_token") -def test_api_search_uuid(cript_api: cript.API) -> None: +def test_api_search_uuid(cript_api: cript.API, dynamic_material_data) -> None: """ tests search with UUID searches for `Sodium polystyrene sulfonate` material via UUID @@ -366,18 +400,12 @@ def test_api_search_uuid(cript_api: cript.API) -> None: 2. takes the UUID from the full node and puts it into the `UUID search` 3. asserts everything is as expected """ - material_name = "Sodium polystyrene sulfonate" - - exact_name_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.EXACT_NAME, value_to_search=material_name) - - material_uuid = exact_name_paginator.current_page_results[0]["uuid"] - - uuid_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.UUID, value_to_search=material_uuid) + uuid_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.UUID, value_to_search=dynamic_material_data["uuid"]) assert isinstance(uuid_paginator, Paginator) assert len(uuid_paginator.current_page_results) == 1 - assert uuid_paginator.current_page_results[0]["name"] == material_name - assert uuid_paginator.current_page_results[0]["uuid"] == material_uuid + assert uuid_paginator.current_page_results[0]["name"] == dynamic_material_data["name"] + assert uuid_paginator.current_page_results[0]["uuid"] == dynamic_material_data["uuid"] @pytest.mark.skipif(not HAS_INTEGRATION_TESTS_ENABLED, reason="requires a real cript_api_token") diff --git a/tests/fixtures/api_fixtures.py b/tests/fixtures/api_fixtures.py new file mode 100644 index 000000000..82391b5ee --- /dev/null +++ b/tests/fixtures/api_fixtures.py @@ -0,0 +1,31 @@ +from typing import Dict + +import pytest + +import cript + + +@pytest.fixture(scope="function") +def dynamic_material_data(cript_api: cript.API) -> Dict[str, str]: + """ + Get the UUID and name of the material, "Sodium polystyrene sulfonate" based on its EXACT_NAME from the API. + + This fixture uses the `cript.API.search` method to fetch the material from the API. + + This can be particularly useful in testing scenarios where you need to perform actions based on the + material's UUID without directly knowing it since the same material will have different UUID + within different server environments such as production, staging, and development. + + Returns + ------- + Dict[str, str] + A dictionary containing {"uuid": uuid, "name": "Sodium polystyrene sulfonate"}. + This provides the test with access to both the name and UUID for verification. + """ + material_name: str = "Sodium polystyrene sulfonate" + + exact_name_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.EXACT_NAME, value_to_search=material_name) + + material_uuid: str = exact_name_paginator.current_page_results[0]["uuid"] + + return {"name": material_name, "uuid": material_uuid} diff --git a/tests/fixtures/primary_nodes.py b/tests/fixtures/primary_nodes.py index ebd7e7852..837994add 100644 --- a/tests/fixtures/primary_nodes.py +++ b/tests/fixtures/primary_nodes.py @@ -3,9 +3,9 @@ import uuid import pytest -from util import strip_uid_from_dict import cript +from tests.utils.util import strip_uid_from_dict @pytest.fixture(scope="function") diff --git a/tests/fixtures/subobjects.py b/tests/fixtures/subobjects.py index 41eff8508..46d95ef1e 100644 --- a/tests/fixtures/subobjects.py +++ b/tests/fixtures/subobjects.py @@ -3,9 +3,9 @@ import uuid import pytest -from util import strip_uid_from_dict import cript +from tests.utils.util import strip_uid_from_dict @pytest.fixture(scope="function") @@ -24,7 +24,6 @@ def complex_parameter_dict() -> dict: return ret_dict -# TODO this fixture should be renamed because it is simple_algorithm_subobject not complex @pytest.fixture(scope="function") def simple_algorithm_node() -> cript.Algorithm: """ diff --git a/tests/nodes/primary_nodes/test_collection.py b/tests/nodes/primary_nodes/test_collection.py index 28dabf6c9..9f6726bcf 100644 --- a/tests/nodes/primary_nodes/test_collection.py +++ b/tests/nodes/primary_nodes/test_collection.py @@ -2,13 +2,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_simple_collection(simple_experiment_node) -> None: @@ -38,14 +37,9 @@ def test_create_complex_collection(simple_experiment_node, simple_inventory_node """ my_collection_name = "my complex collection name" my_cript_doi = "10.1038/1781168a0" + my_collection_notes = "test_create_complex_collection notes" - my_collection = cript.Collection( - name=my_collection_name, - experiment=[simple_experiment_node], - inventory=[simple_inventory_node], - doi=my_cript_doi, - citation=[complex_citation_node], - ) + my_collection = cript.Collection(name=my_collection_name, experiment=[simple_experiment_node], inventory=[simple_inventory_node], doi=my_cript_doi, citation=[complex_citation_node], notes=my_collection_notes) # assertions assert isinstance(my_collection, cript.Collection) @@ -54,6 +48,7 @@ def test_create_complex_collection(simple_experiment_node, simple_inventory_node assert my_collection.inventory == [simple_inventory_node] assert my_collection.doi == my_cript_doi assert my_collection.citation == [complex_citation_node] + assert my_collection.notes == my_collection_notes def test_collection_getters_and_setters(simple_experiment_node, simple_inventory_node, complex_citation_node) -> None: @@ -70,6 +65,7 @@ def test_collection_getters_and_setters(simple_experiment_node, simple_inventory new_collection_name = "my new collection name" new_cript_doi = "my new cript doi" + new_collection_notes = "my collection getters and setters notes" # set Collection attributes my_collection.name = new_collection_name @@ -77,6 +73,7 @@ def test_collection_getters_and_setters(simple_experiment_node, simple_inventory my_collection.inventory = [simple_inventory_node] my_collection.doi = new_cript_doi my_collection.citation = [complex_citation_node] + my_collection.notes = new_collection_notes # assert getters and setters are the same assert isinstance(my_collection, cript.Collection) @@ -85,12 +82,14 @@ def test_collection_getters_and_setters(simple_experiment_node, simple_inventory assert my_collection.inventory == [simple_inventory_node] assert my_collection.doi == new_cript_doi assert my_collection.citation == [complex_citation_node] + assert my_collection.notes == new_collection_notes # remove Collection attributes my_collection.experiment = [] my_collection.inventory = [] my_collection.doi = "" my_collection.citation = [] + my_collection.notes = "" # assert users can remove optional attributes assert my_collection.name == new_collection_name @@ -98,6 +97,7 @@ def test_collection_getters_and_setters(simple_experiment_node, simple_inventory assert my_collection.inventory == [] assert my_collection.doi == "" assert my_collection.citation == [] + assert my_collection.notes == "" def test_serialize_collection_to_json(complex_user_node) -> None: @@ -177,14 +177,14 @@ def test_integration_collection(cript_api, simple_project_node, simple_collectio simple_project_node.collection = [simple_collection_node] # ========= test create ========= - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= simple_project_node.collection[0].doi = "my doi UPDATED" # TODO enable later # simple_project_node.collection[0].notes = "my collection notes UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_collection_node) diff --git a/tests/nodes/primary_nodes/test_computation.py b/tests/nodes/primary_nodes/test_computation.py index 27a327f24..9657cb40c 100644 --- a/tests/nodes/primary_nodes/test_computation.py +++ b/tests/nodes/primary_nodes/test_computation.py @@ -2,13 +2,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_simple_computation_node() -> None: @@ -31,6 +30,7 @@ def test_create_complex_computation_node(simple_data_node, complex_software_conf test that a complex computation node with all possible arguments can be created """ my_computation_type = "analysis" + my_computation_notes = "this is my computation notes" citation = copy.deepcopy(complex_citation_node) condition = copy.deepcopy(complex_condition_node) @@ -43,6 +43,7 @@ def test_create_complex_computation_node(simple_data_node, complex_software_conf condition=[condition], prerequisite_computation=simple_computation_node, citation=[citation], + notes=my_computation_notes, ) # assertions @@ -54,6 +55,7 @@ def test_create_complex_computation_node(simple_data_node, complex_software_conf assert my_computation_node.condition == [condition] assert my_computation_node.prerequisite_computation == simple_computation_node assert my_computation_node.citation == [citation] + assert my_computation_node.notes == my_computation_notes def test_computation_type_invalid_vocabulary() -> None: @@ -139,13 +141,13 @@ def test_integration_computation(cript_api, simple_project_node, simple_computat simple_project_node.name = f"test_integration_computation_name_{uuid.uuid4().hex}" simple_project_node.collection[0].experiment[0].computation = [simple_computation_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # --------- test update --------- # change simple computation attribute to trigger update simple_project_node.collection[0].experiment[0].computation[0].type = "data_fit" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_computation_node) diff --git a/tests/nodes/primary_nodes/test_computational_process.py b/tests/nodes/primary_nodes/test_computational_process.py index 0010d226a..4b84c0a2d 100644 --- a/tests/nodes/primary_nodes/test_computational_process.py +++ b/tests/nodes/primary_nodes/test_computational_process.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_simple_computational_process(simple_data_node, complex_ingredient_node) -> None: @@ -42,6 +41,7 @@ def test_create_complex_computational_process( """ computational_process_name = "my computational process name" + computational_process_notes = "my computational process notes" computational_process_type = "cross_linking" ingredient = complex_ingredient_node @@ -56,6 +56,7 @@ def test_create_complex_computational_process( condition=[complex_condition_node], property=[simple_property_node], citation=[complex_citation_node], + notes=computational_process_notes, ) # assertions @@ -69,6 +70,7 @@ def test_create_complex_computational_process( assert my_computational_process.condition == [complex_condition_node] assert my_computational_process.property == [simple_property_node] assert my_computational_process.citation == [complex_citation_node] + assert my_computational_process.notes == computational_process_notes def test_computational_process_getters_and_setters(simple_computation_process_node, simple_data_node, simple_ingredient_node, simple_software_configuration, simple_condition_node, simple_property_node, complex_citation_node) -> None: @@ -173,13 +175,13 @@ def test_integration_computational_process(cript_api, simple_project_node, simpl simple_project_node.collection[0].experiment[0].computation_process = [simplest_computational_process_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change computational_process to trigger update simple_project_node.collection[0].experiment[0].computation_process[0].type = "DPD" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simplest_computational_process_node) diff --git a/tests/nodes/primary_nodes/test_data.py b/tests/nodes/primary_nodes/test_data.py index 4ade22ba0..6f3f6bcde 100644 --- a/tests/nodes/primary_nodes/test_data.py +++ b/tests/nodes/primary_nodes/test_data.py @@ -1,14 +1,12 @@ -import copy import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_simple_data_node(complex_file_node) -> None: @@ -37,29 +35,32 @@ def test_create_complex_data_node( create a complex data node with all possible arguments """ - file_node = copy.deepcopy(complex_file_node) + my_notes = "my complex data node notes" + my_complex_data = cript.Data( name="my complex data node name", type="afm_amp", - file=[file_node], + file=[complex_file_node], sample_preparation=simple_process_node, computation=[simple_computation_node], computation_process=[simple_computational_process_node], material=[simple_material_node], process=[simple_process_node], # citation=[complex_citation_node], + notes=my_notes, ) # assertions assert isinstance(my_complex_data, cript.Data) assert my_complex_data.type == "afm_amp" - assert my_complex_data.file == [file_node] + assert my_complex_data.file == [complex_file_node] assert my_complex_data.sample_preparation == simple_process_node assert my_complex_data.computation == [simple_computation_node] assert my_complex_data.computation_process == [simple_computational_process_node] assert my_complex_data.material == [simple_material_node] assert my_complex_data.process == [simple_process_node] # assert my_complex_data.citation == [complex_citation_node] + assert my_complex_data.notes == my_notes def test_data_getters_and_setters( @@ -84,6 +85,7 @@ def test_data_getters_and_setters( None """ my_data_type = "afm_height" + my_data_notes = "my data getter setter notes" my_new_files = [ complex_file_node, @@ -106,6 +108,7 @@ def test_data_getters_and_setters( simple_data_node.material = [simple_material_node] simple_data_node.process = [simple_process_node] simple_data_node.citation = [complex_citation_node] + simple_data_node.notes = my_data_notes # assertions check getters and setters assert simple_data_node.type == my_data_type @@ -116,6 +119,7 @@ def test_data_getters_and_setters( assert simple_data_node.material == [simple_material_node] assert simple_data_node.process == [simple_process_node] assert simple_data_node.citation == [complex_citation_node] + assert simple_data_node.notes == my_data_notes # remove optional attributes simple_data_node.sample_preparation = [] @@ -124,6 +128,7 @@ def test_data_getters_and_setters( simple_data_node.material = [] simple_data_node.process = [] simple_data_node.citation = [] + simple_data_node.notes = "" # assert that optional attributes have been removed from data node assert simple_data_node.sample_preparation == [] @@ -132,6 +137,7 @@ def test_data_getters_and_setters( assert simple_data_node.material == [] assert simple_data_node.process == [] assert simple_data_node.citation == [] + assert simple_data_node.notes == "" def test_serialize_data_to_json(simple_data_node) -> None: @@ -178,13 +184,13 @@ def test_integration_data(cript_api, simple_project_node, simple_data_node): simple_project_node.collection[0].experiment[0].data = [simple_data_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # update a simple attribute of data to trigger update simple_project_node.collection[0].experiment[0].data[0].type = "afm_height" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_data_node) diff --git a/tests/nodes/primary_nodes/test_experiment.py b/tests/nodes/primary_nodes/test_experiment.py index c24f421f0..369fa16fd 100644 --- a/tests/nodes/primary_nodes/test_experiment.py +++ b/tests/nodes/primary_nodes/test_experiment.py @@ -2,13 +2,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_simple_experiment() -> None: @@ -224,12 +223,12 @@ def test_integration_experiment(cript_api, simple_project_node, simple_collectio simple_project_node.collection = [simple_collection_node] simple_project_node.collection[0].experiment = [simple_experiment_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # update simple attribute to trigger update simple_project_node.collection[0].experiment[0].funding = ["update1", "update2", "update3"] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_experiment_node) diff --git a/tests/nodes/primary_nodes/test_inventory.py b/tests/nodes/primary_nodes/test_inventory.py index eb1b298c8..708cb2cbf 100644 --- a/tests/nodes/primary_nodes/test_inventory.py +++ b/tests/nodes/primary_nodes/test_inventory.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_get_and_set_inventory(simple_inventory_node) -> None: @@ -22,19 +21,24 @@ def test_get_and_set_inventory(simple_inventory_node) -> None: """ # create new materials material_1 = cript.Material(name="new material 1", identifier=[{"names": ["new material 1 alternative name"]}]) + my_notes = "my inventory notes" - # set inventory materials + # set inventory simple_inventory_node.material = [material_1] + simple_inventory_node.notes = my_notes # get and check inventory materials assert isinstance(simple_inventory_node, cript.Inventory) assert simple_inventory_node.material[-1] == material_1 + assert simple_inventory_node.notes == my_notes # remove optional attributes simple_inventory_node.material = [] + simple_inventory_node.notes = "" # assert that optional attributes have been removed assert simple_inventory_node.material == [] + assert simple_inventory_node.notes == "" def test_inventory_serialization(simple_inventory_node, simple_material_dict) -> None: @@ -73,12 +77,12 @@ def test_integration_inventory(cript_api, simple_project_node, simple_inventory_ simple_project_node.collection[0].inventory = [simple_inventory_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= simple_project_node.collection[0].inventory[0].notes = "inventory notes UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_inventory_node) diff --git a/tests/nodes/primary_nodes/test_material.py b/tests/nodes/primary_nodes/test_material.py index 3e067bbd4..e37aba38c 100644 --- a/tests/nodes/primary_nodes/test_material.py +++ b/tests/nodes/primary_nodes/test_material.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_complex_material(simple_material_node, simple_computational_forcefield_node, simple_process_node) -> None: @@ -18,13 +17,14 @@ def test_create_complex_material(simple_material_node, simple_computational_forc material_name = "my material name" identifier = [{"bigsmiles": "1234"}, {"bigsmiles": "4567"}] keyword = ["acetylene"] + material_notes = "my material notes" component = [simple_material_node] forcefield = [simple_computational_forcefield_node] my_property = [cript.Property(key="modulus_shear", type="min", value=1.23, unit="gram")] - my_material = cript.Material(name=material_name, identifier=identifier, keyword=keyword, component=component, process=simple_process_node, property=my_property, computational_forcefield=forcefield) + my_material = cript.Material(name=material_name, identifier=identifier, keyword=keyword, component=component, process=simple_process_node, property=my_property, computational_forcefield=forcefield, notes=material_notes) assert isinstance(my_material, cript.Material) assert my_material.name == material_name @@ -34,6 +34,7 @@ def test_create_complex_material(simple_material_node, simple_computational_forc assert my_material.process == simple_process_node assert my_material.property == my_property assert my_material.computational_forcefield == forcefield + assert my_material.notes == material_notes def test_invalid_material_keywords() -> None: @@ -54,6 +55,7 @@ def test_all_getters_and_setters(simple_material_node, simple_property_node, sim """ # new attributes new_name = "new material name" + new_notes = "new material notes" new_identifier = [{"bigsmiles": "6789"}] @@ -83,6 +85,7 @@ def test_all_getters_and_setters(simple_material_node, simple_property_node, sim simple_material_node.computational_forcefield = simple_computational_forcefield_node simple_material_node.keyword = new_material_keywords simple_material_node.component = new_components + simple_material_node.notes = new_notes # get all attributes and assert that they are equal to the setter assert simple_material_node.name == new_name @@ -92,18 +95,21 @@ def test_all_getters_and_setters(simple_material_node, simple_property_node, sim assert simple_material_node.computational_forcefield == simple_computational_forcefield_node assert simple_material_node.keyword == new_material_keywords assert simple_material_node.component == new_components + assert simple_material_node.notes == new_notes # remove optional attributes simple_material_node.property = [] simple_material_node.parent_material = None simple_material_node.computational_forcefield = None simple_material_node.component = [] + simple_material_node.notes = "" # assert optional attributes have been removed assert simple_material_node.property == [] assert simple_material_node.parent_material is None assert simple_material_node.computational_forcefield is None assert simple_material_node.component == [] + assert simple_material_node.notes == "" def test_serialize_material_to_json(complex_material_dict, complex_material_node) -> None: @@ -140,13 +146,13 @@ def test_integration_material(cript_api, simple_project_node, simple_material_no simple_project_node.material = [simple_material_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # update material attribute to trigger update simple_project_node.material[0].identifier = [{"bigsmiles": "my bigsmiles UPDATED"}] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_material_node) diff --git a/tests/nodes/primary_nodes/test_process.py b/tests/nodes/primary_nodes/test_process.py index 9abe92511..ebd58774f 100644 --- a/tests/nodes/primary_nodes/test_process.py +++ b/tests/nodes/primary_nodes/test_process.py @@ -1,14 +1,12 @@ -import copy import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_simple_process() -> None: @@ -31,7 +29,7 @@ def test_simple_process() -> None: assert my_process.keyword == my_process_keywords -def test_complex_process_node(complex_ingredient_node, simple_equipment_node, complex_citation_node, simple_property_node, simple_condition_node, simple_material_node, simple_process_node, complex_equipment_node, complex_condition_node) -> None: +def test_complex_process_node(complex_ingredient_node, complex_citation_node, simple_property_node, simple_material_node, simple_process_node, complex_equipment_node, complex_condition_node) -> None: """ create a process node with all possible arguments @@ -39,8 +37,6 @@ def test_complex_process_node(complex_ingredient_node, simple_equipment_node, co ----- * indirectly tests the vocabulary as well, as it gives it valid vocabulary """ - # TODO clean up this test and use fixtures from conftest.py - my_process_name = "my complex process node name" my_process_type = "affinity_pure" my_process_description = "my simple material description" @@ -54,9 +50,7 @@ def test_complex_process_node(complex_ingredient_node, simple_equipment_node, co "annealing_sol", ] - # create complex process - citation = copy.deepcopy(complex_citation_node) - prop = cript.Property("n_neighbor", "value", 2.0, None) + my_notes = "my complex process notes" my_complex_process = cript.Process( name=my_process_name, @@ -68,9 +62,10 @@ def test_complex_process_node(complex_ingredient_node, simple_equipment_node, co waste=process_waste, prerequisite_process=[simple_process_node], condition=[complex_condition_node], - property=[prop], + property=[simple_property_node], keyword=my_process_keywords, - citation=[citation], + citation=[complex_citation_node], + notes=my_notes, ) # assertions assert my_complex_process.type == my_process_type @@ -81,9 +76,10 @@ def test_complex_process_node(complex_ingredient_node, simple_equipment_node, co assert my_complex_process.waste == process_waste assert my_complex_process.prerequisite_process[-1] == simple_process_node assert my_complex_process.condition[-1] == complex_condition_node - assert my_complex_process.property[-1] == prop + assert my_complex_process.property[-1] == simple_property_node assert my_complex_process.keyword[-1] == my_process_keywords[-1] - assert my_complex_process.citation[-1] == citation + assert my_complex_process.citation[-1] == complex_citation_node + assert my_complex_process.notes == my_notes def test_process_getters_and_setters( @@ -108,36 +104,35 @@ def test_process_getters_and_setters( new_process_type = "blow_molding" new_process_description = "my new process description" new_process_keywords = "annealing_sol" + new_process_notes = "new process notes" # test setters simple_process_node.type = new_process_type simple_process_node.ingredient = [complex_ingredient_node] simple_process_node.description = new_process_description - equipment = complex_equipment_node - simple_process_node.equipment = [equipment] - product = simple_material_node - simple_process_node.product = [product] + simple_process_node.equipment = [complex_equipment_node] + simple_process_node.product = [simple_material_node] simple_process_node.waste = [simple_material_node] simple_process_node.prerequisite_process = [simple_process_node] simple_process_node.condition = [complex_condition_node] - prop = cript.Property("n_neighbor", "value", 2.0, None) - simple_process_node.property += [prop] + simple_process_node.property += [simple_property_node] simple_process_node.keyword = [new_process_keywords] - citation = copy.deepcopy(complex_citation_node) - simple_process_node.citation = [citation] + simple_process_node.citation = [complex_citation_node] + simple_process_node.notes = new_process_notes # test getters assert simple_process_node.type == new_process_type assert simple_process_node.ingredient == [complex_ingredient_node] assert simple_process_node.description == new_process_description - assert simple_process_node.equipment[-1] == equipment - assert simple_process_node.product[-1] == product + assert simple_process_node.equipment[-1] == complex_equipment_node + assert simple_process_node.product[-1] == simple_material_node assert simple_process_node.waste == [simple_material_node] assert simple_process_node.prerequisite_process == [simple_process_node] assert simple_process_node.condition == [complex_condition_node] - assert simple_process_node.property[-1] == prop + assert simple_process_node.property[-1] == simple_property_node assert simple_process_node.keyword == [new_process_keywords] - assert simple_process_node.citation[-1] == citation + assert simple_process_node.citation[-1] == complex_citation_node + assert simple_process_node.notes == new_process_notes # test that optional attributes can be successfully removed simple_process_node.ingredient = [] @@ -150,6 +145,7 @@ def test_process_getters_and_setters( simple_process_node.property = [] simple_process_node.keyword = [] simple_process_node.citation = [] + simple_process_node.notes = "" # assert that optional attributes have been removed assert simple_process_node.ingredient == [] @@ -162,6 +158,7 @@ def test_process_getters_and_setters( assert simple_process_node.property == [] assert simple_process_node.keyword == [] assert simple_process_node.citation == [] + assert simple_process_node.notes == "" def test_serialize_process_to_json(simple_process_node) -> None: @@ -188,7 +185,7 @@ def test_integration_simple_process(cript_api, simple_project_node, simple_proce simple_project_node.collection[0].experiment[0].process = [simple_process_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) def test_integration_complex_process(cript_api, simple_project_node, simple_process_node, simple_material_node): @@ -210,13 +207,13 @@ def test_integration_complex_process(cript_api, simple_project_node, simple_proc simple_project_node.collection[0].experiment[0].process = [simple_process_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].process[0].description = "process description UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_process_node) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 5569a99ac..3812a1e60 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_create_simple_project(simple_collection_node) -> None: @@ -34,24 +33,29 @@ def test_project_getters_and_setters(simple_project_node, simple_collection_node 4. what was set and what was gotten should be equivalent """ new_project_name = "my new project name" + new_project_notes = "my new project notes" # set attributes simple_project_node.name = new_project_name simple_project_node.collection = [complex_collection_node] simple_project_node.material = [simple_material_node] + simple_project_node.notes = new_project_notes # get attributes and assert that they are the same assert simple_project_node.name == new_project_name assert simple_project_node.collection == [complex_collection_node] assert simple_project_node.material == [simple_material_node] + assert simple_project_node.notes == new_project_notes # remove optional attributes simple_project_node.collection = [] simple_project_node.material = [] + simple_project_node.notes = "" # assert optional attributes have been removed assert simple_project_node.collection == [] assert simple_project_node.material == [] + assert simple_project_node.notes == "" def test_serialize_project_to_json(complex_project_node, complex_project_dict) -> None: @@ -82,12 +86,12 @@ def test_integration_project(cript_api, simple_project_node): # ========= test create ========= simple_project_node.name = f"test_integration_project_name_{uuid.uuid4().hex}" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= simple_project_node.notes = "project notes UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_project_node) diff --git a/tests/nodes/primary_nodes/test_reference.py b/tests/nodes/primary_nodes/test_reference.py index 6348d67b6..16543e9d0 100644 --- a/tests/nodes/primary_nodes/test_reference.py +++ b/tests/nodes/primary_nodes/test_reference.py @@ -3,14 +3,14 @@ import warnings import pytest -from integration_test_helper import ( - delete_integration_node_helper, - integrate_nodes_helper, -) -from util import strip_uid_from_dict import cript from cript.api.exceptions import APIError +from tests.utils.integration_test_helper import ( + delete_integration_node_helper, + save_integration_node_helper, +) +from tests.utils.util import strip_uid_from_dict def test_create_simple_reference() -> None: @@ -212,7 +212,7 @@ def test_integration_reference(cript_api, simple_project_node, complex_citation_ simple_project_node.collection[0].citation = [complex_citation_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # TODO deserialization with citation in collection is wrong # raise Exception("Citation is missing from collection node from API") @@ -224,7 +224,7 @@ def test_integration_reference(cript_api, simple_project_node, complex_citation_ # complex_reference_node.type = "book" simple_project_node.collection[0].citation[0].reference.title = "reference title UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= # isolate reference from citation diff --git a/tests/nodes/subobjects/test_algorithm.py b/tests/nodes/subobjects/test_algorithm.py index bcc5ddfa5..b8e1ae515 100644 --- a/tests/nodes/subobjects/test_algorithm.py +++ b/tests/nodes/subobjects/test_algorithm.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_setter_getter(simple_algorithm_node, complex_citation_node, complex_parameter_node): @@ -62,13 +61,13 @@ def test_integration_algorithm(cript_api, simple_project_node, simple_collection simple_project_node.collection[0].experiment[0].computation[0].software_configuration = [simple_software_configuration] simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].algorithm = [simple_algorithm_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change a simple attribute to trigger update simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].algorithm[0].type = "integration" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_algorithm_node) diff --git a/tests/nodes/subobjects/test_citation.py b/tests/nodes/subobjects/test_citation.py index b87e077ea..76633776c 100644 --- a/tests/nodes/subobjects/test_citation.py +++ b/tests/nodes/subobjects/test_citation.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_json(complex_citation_node, complex_citation_dict): @@ -47,13 +46,13 @@ def test_integration_citation(cript_api, simple_project_node, simple_collection_ simple_project_node.collection[0].citation = [complex_citation_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].citation[0].type = "extracted_by_human" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=complex_citation_node) diff --git a/tests/nodes/subobjects/test_computational_forcefield.py b/tests/nodes/subobjects/test_computational_forcefield.py index aa1c5b29e..93aae5e01 100644 --- a/tests/nodes/subobjects/test_computational_forcefield.py +++ b/tests/nodes/subobjects/test_computational_forcefield.py @@ -2,13 +2,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_computational_forcefield(complex_computational_forcefield_node, complex_computational_forcefield_dict): @@ -81,12 +80,12 @@ def test_integration_computational_forcefield(cript_api, simple_project_node, si simple_project_node.material = [simple_material_node] simple_project_node.material[0].computational_forcefield = simple_computational_forcefield_node - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.material[0].computational_forcefield.description = "material computational_forcefield description UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_computational_forcefield_node) diff --git a/tests/nodes/subobjects/test_condition.py b/tests/nodes/subobjects/test_condition.py index 3878ac133..9847863be 100644 --- a/tests/nodes/subobjects/test_condition.py +++ b/tests/nodes/subobjects/test_condition.py @@ -1,11 +1,11 @@ import json import uuid -from integration_test_helper import ( +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict +from tests.utils.util import strip_uid_from_dict def test_json(complex_condition_node, complex_condition_dict): @@ -81,13 +81,13 @@ def test_integration_process_condition(cript_api, simple_project_node, simple_co simple_project_node.collection[0].experiment[0].computation[0].condition = [simple_condition_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].computation[0].condition[0].descriptor = "condition descriptor UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_condition_node) diff --git a/tests/nodes/subobjects/test_equipment.py b/tests/nodes/subobjects/test_equipment.py index dcab6caec..960757e4c 100644 --- a/tests/nodes/subobjects/test_equipment.py +++ b/tests/nodes/subobjects/test_equipment.py @@ -2,11 +2,11 @@ import json import uuid -from integration_test_helper import ( +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict +from tests.utils.util import strip_uid_from_dict def test_json(complex_equipment_node, complex_equipment_dict): @@ -69,13 +69,13 @@ def test_integration_equipment(cript_api, simple_project_node, simple_collection simple_project_node.collection[0].experiment[0].process = [simple_process_node] simple_project_node.collection[0].experiment[0].process[0].equipment = [simple_equipment_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].process[0].equipment[0].description = "equipment description UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_equipment_node) diff --git a/tests/nodes/subobjects/test_ingredient.py b/tests/nodes/subobjects/test_ingredient.py index a4d73bc82..09865932e 100644 --- a/tests/nodes/subobjects/test_ingredient.py +++ b/tests/nodes/subobjects/test_ingredient.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_json(complex_ingredient_node, complex_ingredient_dict): @@ -66,13 +65,13 @@ def test_integration_ingredient(cript_api, simple_project_node, simple_collectio # add orphaned material node to project simple_project_node.material = [simple_material_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].process[0].ingredient[0].keyword = ["polymer"] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_ingredient_node) diff --git a/tests/nodes/subobjects/test_parameter.py b/tests/nodes/subobjects/test_parameter.py index b16cc3bca..0eb8018de 100644 --- a/tests/nodes/subobjects/test_parameter.py +++ b/tests/nodes/subobjects/test_parameter.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_parameter_setter_getter(complex_parameter_node): @@ -53,13 +52,13 @@ def test_integration_parameter(cript_api, simple_project_node, simple_collection simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].algorithm = [simple_algorithm_node] simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].algorithm[0].parameter = [complex_parameter_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # update simple attribute to trigger update simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].algorithm[0].parameter[0].value = 123456789 - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=complex_parameter_node) diff --git a/tests/nodes/subobjects/test_property.py b/tests/nodes/subobjects/test_property.py index 987c07235..bd1d0eeb2 100644 --- a/tests/nodes/subobjects/test_property.py +++ b/tests/nodes/subobjects/test_property.py @@ -2,13 +2,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_json(complex_property_node, complex_property_dict): @@ -112,12 +111,12 @@ def test_integration_material_property(cript_api, simple_project_node, simple_ma simple_project_node.material = [simple_material_node] simple_project_node.material[0].property = [simple_property_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.material[0].property[0].notes = "property sub-object notes UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_property_node) diff --git a/tests/nodes/subobjects/test_quantity.py b/tests/nodes/subobjects/test_quantity.py index 3a3f6577d..8b04cca78 100644 --- a/tests/nodes/subobjects/test_quantity.py +++ b/tests/nodes/subobjects/test_quantity.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_json(complex_quantity_node, complex_quantity_dict): @@ -60,12 +59,12 @@ def test_integration_quantity(cript_api, simple_project_node, simple_collection_ # add orphaned material node to project simple_project_node.material = [simple_material_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].process[0].ingredient[0].quantity[0].value = 123456789 - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= # isolate quantity from ingredient diff --git a/tests/nodes/subobjects/test_software.py b/tests/nodes/subobjects/test_software.py index 992ffbaf5..02c732f28 100644 --- a/tests/nodes/subobjects/test_software.py +++ b/tests/nodes/subobjects/test_software.py @@ -3,14 +3,14 @@ import uuid import pytest -from integration_test_helper import ( - delete_integration_node_helper, - integrate_nodes_helper, -) -from util import strip_uid_from_dict import cript from cript.api.exceptions import APIError +from tests.utils.integration_test_helper import ( + delete_integration_node_helper, + save_integration_node_helper, +) +from tests.utils.util import strip_uid_from_dict def test_json(complex_software_node, complex_software_dict): @@ -72,12 +72,12 @@ def test_integration_software(cript_api, simple_project_node, simple_computation simple_project_node.collection[0].experiment[0].computation[0].software_configuration = [simple_software_configuration] simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].software = complex_software_node - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].software.version = "software version UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= # software nodes are frozen nodes and cannot be deleted diff --git a/tests/nodes/subobjects/test_software_configuration.py b/tests/nodes/subobjects/test_software_configuration.py index 7bc1f102e..36b6ee1d3 100644 --- a/tests/nodes/subobjects/test_software_configuration.py +++ b/tests/nodes/subobjects/test_software_configuration.py @@ -1,13 +1,12 @@ import json import uuid -from integration_test_helper import ( +import cript +from tests.utils.integration_test_helper import ( delete_integration_node_helper, - integrate_nodes_helper, + save_integration_node_helper, ) -from util import strip_uid_from_dict - -import cript +from tests.utils.util import strip_uid_from_dict def test_json(complex_software_configuration_node, complex_software_configuration_dict): @@ -19,26 +18,32 @@ def test_json(complex_software_configuration_node, complex_software_configuratio assert strip_uid_from_dict(json.loads(sc2.json)) == strip_uid_from_dict(json.loads(sc.json)) -def test_setter_getter(complex_software_configuration_node, simple_algorithm_node, complex_citation_node): - sc2 = complex_software_configuration_node - software2 = sc2.software - sc2.software = software2 - assert sc2.software is software2 +def test_setter_getter(simple_software_configuration, simple_algorithm_node, complex_citation_node): + """ + test setters and getters for `SoftwareConfiguration` and be sure it works fine + also test that the node can be set and also reset + """ + new_notes: str = "my new notes" + + # use setters + simple_software_configuration.algorithm = [simple_algorithm_node] + simple_software_configuration.citation = [complex_citation_node] + simple_software_configuration.notes = new_notes - # assert len(sc2.algorithm) == 1 - # al2 = simple_algorithm_node - # print(sc2.get_json(indent=2,sortkeys=False).json) - # print(al2.get_json(indent=2,sortkeys=False).json) - # sc2.algorithm += [al2] - # assert sc2.algorithm[1] is al2 + # assert getters and setters are same + assert simple_software_configuration.algorithm == [simple_algorithm_node] + assert simple_software_configuration.citation == [complex_citation_node] + assert simple_software_configuration.notes == new_notes - sc2.notes = "my new fancy notes" - assert sc2.notes == "my new fancy notes" + # remove optional attributes + simple_software_configuration.algorithm = [] + simple_software_configuration.citation = [] + simple_software_configuration.notes = "" - # cit2 = complex_citation_node - # assert len(sc2.citation) == 1 - # sc2.citation += [cit2] - # assert sc2.citation[1] == cit2 + # assert that optional attributes have been removed from data node + assert simple_software_configuration.algorithm == [] + assert simple_software_configuration.citation == [] + assert simple_software_configuration.notes == "" def test_integration_software_configuration(cript_api, simple_project_node, simple_collection_node, simple_experiment_node, simple_computation_node, simple_software_configuration): @@ -57,13 +62,13 @@ def test_integration_software_configuration(cript_api, simple_project_node, simp simple_project_node.collection[0].experiment[0].computation = [simple_computation_node] simple_project_node.collection[0].experiment[0].computation[0].software_configuration = [simple_software_configuration] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update simple_project_node.collection[0].experiment[0].computation[0].software_configuration[0].notes = "software configuration integration test UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_software_configuration) diff --git a/tests/nodes/supporting_nodes/test_file.py b/tests/nodes/supporting_nodes/test_file.py index 905fe1d71..e3003a416 100644 --- a/tests/nodes/supporting_nodes/test_file.py +++ b/tests/nodes/supporting_nodes/test_file.py @@ -4,20 +4,20 @@ import uuid import pytest -from integration_test_helper import ( - delete_integration_node_helper, - integrate_nodes_helper, -) -from util import strip_uid_from_dict import cript +from tests.utils.integration_test_helper import ( + delete_integration_node_helper, + save_integration_node_helper, +) +from tests.utils.util import strip_uid_from_dict def test_create_file() -> None: """ tests that a simple file with only required attributes can be created """ - file_node = cript.File(name="my file name", source="https://google.com", type="calibration") + file_node = cript.File(name="my file name", source="https://google.com", type="calibration", extension=".csv") assert isinstance(file_node, cript.File) @@ -90,7 +90,7 @@ def test_local_file_source_upload_and_download(tmp_path_factory) -> None: local_file_path.write_text(file_text) # create a file node with a local file path - my_file = cript.File(name="my local file source node", source=str(local_file_path), type="data") + my_file = cript.File(name="my local file source node", source=str(local_file_path), type="data", extension=".txt") # check that the file source has been uploaded to cloud storage and source has changed to reflect that assert my_file.source.startswith("tests/") @@ -124,7 +124,7 @@ def test_create_file_with_local_source(tmp_path) -> None: with open(file_path, "w") as temporary_file: temporary_file.write("hello world!") - assert cript.File(name="my file node with local source", source=str(file_path), type="calibration") + assert cript.File(name="my file node with local source", source=str(file_path), type="calibration", extension=".txt") def test_file_getters_and_setters(complex_file_node) -> None: @@ -140,26 +140,31 @@ def test_file_getters_and_setters(complex_file_node) -> None: new_file_type = "computation_config" new_file_extension = ".csv" new_data_dictionary = "new data dictionary" + new_notes = "new notes" # ------- set properties ------- complex_file_node.source = new_source complex_file_node.type = new_file_type complex_file_node.extension = new_file_extension complex_file_node.data_dictionary = new_data_dictionary + complex_file_node.notes = new_notes # ------- assert set and get properties are the same ------- assert complex_file_node.source == new_source assert complex_file_node.type == new_file_type assert complex_file_node.extension == new_file_extension assert complex_file_node.data_dictionary == new_data_dictionary + assert complex_file_node.notes == new_notes # remove optional attributes complex_file_node.extension = "" complex_file_node.data_dictionary = "" + complex_file_node.notes = "" # assert optional attributes have been removed assert complex_file_node.extension == "" assert complex_file_node.data_dictionary == "" + assert complex_file_node.notes == "" def test_serialize_file_to_json(complex_file_node) -> None: @@ -212,7 +217,7 @@ def test_integration_file(cript_api, simple_project_node, simple_data_node): simple_project_node.collection[0].experiment[0].data = [simple_data_node] - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test update ========= # change simple attribute to trigger update @@ -220,7 +225,7 @@ def test_integration_file(cript_api, simple_project_node, simple_data_node): # TODO enable later # simple_project_node.collection[0].experiment[0].data[0].file[0].data_dictionary = "file data_dictionary UPDATED" - integrate_nodes_helper(cript_api=cript_api, project_node=simple_project_node) + save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) # ========= test delete ========= # isolated file node from data node diff --git a/tests/nodes/supporting_nodes/test_user.py b/tests/nodes/supporting_nodes/test_user.py index ef2ee7ef9..273b0c034 100644 --- a/tests/nodes/supporting_nodes/test_user.py +++ b/tests/nodes/supporting_nodes/test_user.py @@ -1,9 +1,9 @@ import json import pytest -from util import strip_uid_from_dict import cript +from tests.utils.util import strip_uid_from_dict def test_user_serialization_and_deserialization(complex_user_dict, complex_user_node): diff --git a/tests/nodes/test_utils.py b/tests/nodes/test_utils.py index 14826bafc..d54aa4b7e 100644 --- a/tests/nodes/test_utils.py +++ b/tests/nodes/test_utils.py @@ -1,3 +1,4 @@ +import cript from cript.nodes.util import _is_node_field_valid @@ -16,3 +17,37 @@ def test_is_node_field_valid() -> None: assert _is_node_field_valid(node_type_list="Project") is False assert _is_node_field_valid(node_type_list=[]) is False + + +def test_load_node_from_json_dict_argument() -> None: + """ + tests that `cript.load_nodes_from_json` can correctly load the material node from a dict + instead of JSON string + """ + material_name = "my material name" + material_notes = "my material node notes" + material_bigsmiles = "my bigsmiles" + material_uuid = "29c796a1-8f08-41ea-8524-29e925f384af" + + material_dict = { + "node": ["Material"], + "uid": f"_:{material_uuid}", + "uuid": material_uuid, + "name": material_name, + "notes": material_notes, + "property": [{"node": ["Property"], "uid": "_:aedce614-7acb-49d2-a2f6-47463f15b707", "uuid": "aedce614-7acb-49d2-a2f6-47463f15b707", "key": "modulus_shear", "type": "value", "value": 5.0, "unit": "GPa"}], + "computational_forcefield": {"node": ["ComputationalForcefield"], "uid": "_:059952a3-20f2-4739-96bd-a5ea43068065", "uuid": "059952a3-20f2-4739-96bd-a5ea43068065", "key": "amber", "building_block": "atom"}, + "keyword": ["acetylene"], + "bigsmiles": material_bigsmiles, + } + + # convert material from dict to node + my_material_node_from_dict = cript.load_nodes_from_json(nodes_json=material_dict) + + # assert material is correctly deserialized from JSON dict to Material Python object + assert type(my_material_node_from_dict) == cript.Material + assert my_material_node_from_dict.name == material_name + assert my_material_node_from_dict.identifier[0]["bigsmiles"] == "my bigsmiles" + + # convert UUID object to UUID str and compare + assert str(my_material_node_from_dict.uuid) == material_uuid diff --git a/tests/test_node_util.py b/tests/test_node_util.py index 3b360b683..d4e4e4551 100644 --- a/tests/test_node_util.py +++ b/tests/test_node_util.py @@ -3,7 +3,6 @@ from dataclasses import replace import pytest -from util import strip_uid_from_dict import cript from cript.nodes.core import get_new_uid @@ -17,6 +16,7 @@ CRIPTOrphanedMaterialError, CRIPTOrphanedProcessError, ) +from tests.utils.util import strip_uid_from_dict def test_removing_nodes(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): @@ -323,3 +323,25 @@ def test_invalid_project_graphs(simple_project_node, simple_material_node, simpl cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) project.validate() + + +def test_expanded_json(complex_project_node): + """ + Tests the generation and deserialization of expanded JSON for a complex project node. + + This test verifies 2 key aspects: + 1. A complex project node can be serialized into an expanded JSON string, without UUID placeholders. + 2. The expanded JSON can be deserialized into a node that is equivalent to the original node. + """ + project_expanded_json: str = complex_project_node.get_expanded_json() + deserialized_project_node: cript.Project = cript.load_nodes_from_json(project_expanded_json) + + # assert the expanded JSON was correctly deserialized to project node + assert deserialized_project_node == complex_project_node + + condensed_json: str = complex_project_node.json + + # since short JSON has UUID it will not be able to deserialize correctly and will + # raise CRIPTJsonDeserializationError + with pytest.raises(cript.nodes.exceptions.CRIPTJsonDeserializationError): + cript.load_nodes_from_json(condensed_json) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration_test_helper.py b/tests/utils/integration_test_helper.py similarity index 98% rename from tests/integration_test_helper.py rename to tests/utils/integration_test_helper.py index 83e89ccff..0639a9028 100644 --- a/tests/integration_test_helper.py +++ b/tests/utils/integration_test_helper.py @@ -1,14 +1,14 @@ import json import pytest -from conftest import HAS_INTEGRATION_TESTS_ENABLED from deepdiff import DeepDiff import cript +from conftest import HAS_INTEGRATION_TESTS_ENABLED from cript.nodes.uuid_base import UUIDBaseNode -def integrate_nodes_helper(cript_api: cript.API, project_node: cript.Project): +def save_integration_node_helper(cript_api: cript.API, project_node: cript.Project): """ integration test between Python SDK and API Client tests both POST and GET diff --git a/tests/util.py b/tests/utils/util.py similarity index 100% rename from tests/util.py rename to tests/utils/util.py