From a0c03220d6cfc27544cf29c26a02a956cfbd143a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 5 Apr 2024 15:31:04 +0200 Subject: [PATCH 001/114] add unit conversion utility using qudt --- dsms/core/configuration.py | 5 + dsms/knowledge/semantics/__init__.py | 1 + dsms/knowledge/semantics/units.py | 143 +++++++++++++++++++++++++++ setup.cfg | 2 + tests/conftest.py | 16 ++- tests/test_utils.py | 16 +++ 6 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 dsms/knowledge/semantics/__init__.py create mode 100644 dsms/knowledge/semantics/units.py diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index b400b23..b853c8e 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -53,6 +53,11 @@ class Configuration(BaseSettings): description="Repository of the triplestore for KItems in the DSMS", ) + qudt_uri: AnyUrl = Field( + "http://qudt.org/2.1/vocab/unit", + description="URI to QUDT Ontology for unit conversion", + ) + @field_validator("token") def validate_auth(cls, val, info: ValidationInfo): """Validate the provided authentication/authorization secrets.""" diff --git a/dsms/knowledge/semantics/__init__.py b/dsms/knowledge/semantics/__init__.py new file mode 100644 index 0000000..3c31da6 --- /dev/null +++ b/dsms/knowledge/semantics/__init__.py @@ -0,0 +1 @@ +"""DSMS Semantics Module""" diff --git a/dsms/knowledge/semantics/units.py b/dsms/knowledge/semantics/units.py new file mode 100644 index 0000000..113fa63 --- /dev/null +++ b/dsms/knowledge/semantics/units.py @@ -0,0 +1,143 @@ +"""DSMS Unit Semantics Conversion""" + +import tempfile +from functools import lru_cache +from typing import List, Optional +from urllib.parse import urlparse + +import requests +from rdflib import Graph + + +def _is_valid_url(url): + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + +def _qudt_sparql(symbol: str) -> str: + return f"""PREFIX qudt: + SELECT DISTINCT ?unit + WHERE {{ + ?unit a qudt:Unit . + {{ + ?unit qudt:symbol "{symbol}" . + }} + UNION + {{ + ?unit qudt:ucumCode "{symbol}"^^qudt:UCUMcs . + }} + }}""" + + +def _qudt_sparql_factor(uri: str) -> str: + return f"""PREFIX qudt: + SELECT DISTINCT ?factor + WHERE {{ + <{uri}> a qudt:Unit ; + qudt:conversionMultiplier ?factor . + }}""" + + +@lru_cache +def _get_qudt_ontology() -> requests.Response: + from dsms import Context + + url = Context.dsms.config.qudt_uri + response = requests.get(url, timeout=Context.dsms.config.request_timeout) + if response.status_code != 200: + raise RuntimeError( + f"Could not download QUDT ontology. Please check URI: {url}" + ) + response.encoding = "utf-8" + return response + + +def _to_tempfile(content) -> str: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".ttl", delete=False, encoding="utf-8" + ) as tmp: + tmp.write(content) + return tmp.name + + +@lru_cache +def _get_qudt_graph() -> Graph: + response = _get_qudt_ontology() + file = _to_tempfile(response.text) + + graph = Graph() + graph.parse(file, encoding="utf-8") + return graph + + +@lru_cache +def _get_query_match(symbol: str) -> List[str]: + graph = _get_qudt_graph() + query = _qudt_sparql(symbol) + return [str(row["unit"]) for row in graph.query(query)] + + +def _check_qudt_mapping(symbol: str) -> Optional[str]: + match = _get_query_match(symbol) + if len(match) == 0: + raise ValueError( + f"No QUDT Mapping found for unit with symbol `{symbol}`." + ) + if len(match) > 1: + raise ValueError( + f"More than one QUDT Mapping found for unit with symbol `{symbol}`." + ) + return match.pop() + + +@lru_cache +def get_conversion_factor(original_unit: str, target_unit: str) -> float: + """ + Calculate the conversion factor between two units. + + This function calculates the conversion factor between two units, specified + by their URIs or labels. If the provided units are not valid URLs, the + function attempts to check a QUDT (Quantities, Units, Dimensions, and Data + Types in OWL and XML) mapping to retrieve the correct URI. + + Parameters: + original_unit (str): The original unit to convert from, specified by URI or unit symbol. + target_unit (str): The target unit to convert to, specified by URI or label. + + Returns: + float: The conversion factor from the original unit to the target unit. + + Raises: + ValueError: If the conversion factor cannot be determined due to invalid + unit specifications or missing mapping in QUDT. + + Example: + >>> get_conversion_factor('http://qudt.org/vocab/unit/M', 'http://qudt.org/vocab/unit/IN') + 39.3701 + >>> get_conversion_factor('m', 'in') + 39.3701 + """ + if not _is_valid_url(original_unit): + original_unit = _check_qudt_mapping(original_unit) + if not _is_valid_url(target_unit): + target_unit = _check_qudt_mapping(target_unit) + return _get_factor_from_uri(original_unit) / _get_factor_from_uri( + target_unit + ) + + +@lru_cache +def _get_factor_from_uri(uri: str) -> int: + graph = _get_qudt_graph() + query = _qudt_sparql_factor(uri) + factor = [float(row["factor"]) for row in graph.query(query)] + if len(factor) == 0: + raise ValueError(f"No conversion factor for unit with uri `{uri}`.") + if len(factor) > 1: + raise ValueError( + f"More than one conversion factor for unit with uri `{uri}`." + ) + return factor.pop() diff --git a/setup.cfg b/setup.cfg index 7681dad..e9fb3cf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,8 @@ classifiers = [options] packages = find: install_requires = + html5lib>=1,<2 + lru-cache<1 pandas>=2,<3 pydantic>=2 pydantic-settings diff --git a/tests/conftest.py b/tests/conftest.py index ec587f4..1454475 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import responses if TYPE_CHECKING: - from typing import Any, Dict + from typing import Any, Dict, List class MockDB: @@ -66,6 +66,14 @@ def mock_responses(custom_address) -> "Dict[str, Any]": } +@pytest.fixture(scope="function") +def passthru() -> "List[str]": + return [ + "https://qudt.org/2.1/vocab/unit", + "http://qudt.org/2.1/vocab/unit", + ] + + @pytest.fixture(scope="function") def mock_callbacks(custom_address) -> "Dict[str, Any]": ktypes = urljoin(custom_address, "api/knowledge-type/") @@ -153,7 +161,11 @@ def _get_hdf5() -> "Dict[str, Any]": @pytest.fixture(autouse=True, scope="function") -def register_mocks(mock_responses, mock_callbacks, custom_address) -> str: +def register_mocks( + mock_responses, mock_callbacks, passthru, custom_address +) -> str: + for url in passthru: + responses.add_passthru(url) for url, endpoints in mock_responses.items(): for response in endpoints: responses.add( diff --git a/tests/test_utils.py b/tests/test_utils.py index da3d36a..2a00cbd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -109,3 +109,19 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): } diffs = _get_kitems_diffs(kitem_old, kitem_new) assert sorted(diffs) == sorted(expected) + + +@responses.activate +def test_unit_conversion(custom_address): + """Test unit conversion test""" + from dsms import DSMS + from dsms.knowledge.semantics.units import get_conversion_factor + + with pytest.warns(UserWarning, match="No authentication details"): + DSMS(host_url=custom_address) + + assert round(get_conversion_factor("mm", "m"), 3) == 0.001 + + assert round(get_conversion_factor("km", "in"), 1) == 39370.1 + + assert round(get_conversion_factor("GPa", "MPa")) == 1000 From 39689ca268826be59663a8bd0d19e529dba992fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 5 Apr 2024 17:28:41 +0200 Subject: [PATCH 002/114] improve code - check unit compability, add example --- dsms/core/configuration.py | 9 +- dsms/knowledge/semantics/units.py | 169 +++++++++++++++++------------- examples/get_units.py | 37 +++++++ tests/conftest.py | 2 + tests/test_utils.py | 18 +++- 5 files changed, 157 insertions(+), 78 deletions(-) create mode 100644 examples/get_units.py diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index b853c8e..5adf1e1 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -53,9 +53,14 @@ class Configuration(BaseSettings): description="Repository of the triplestore for KItems in the DSMS", ) - qudt_uri: AnyUrl = Field( + qudt_units: AnyUrl = Field( "http://qudt.org/2.1/vocab/unit", - description="URI to QUDT Ontology for unit conversion", + description="URI to QUDT Unit ontology for unit conversion", + ) + + qudt_quantity_kinds: AnyUrl = Field( + "http://qudt.org/vocab/quantitykind/", + description="URI to QUDT quantity kind ontology for unit conversion", ) @field_validator("token") diff --git a/dsms/knowledge/semantics/units.py b/dsms/knowledge/semantics/units.py index 113fa63..d6e964e 100644 --- a/dsms/knowledge/semantics/units.py +++ b/dsms/knowledge/semantics/units.py @@ -1,14 +1,63 @@ """DSMS Unit Semantics Conversion""" -import tempfile from functools import lru_cache -from typing import List, Optional +from io import StringIO +from typing import Optional from urllib.parse import urlparse import requests from rdflib import Graph +@lru_cache +def get_conversion_factor( + original_unit: str, target_unit: str, rounded: Optional[int] = None +) -> float: + """ + Calculate the conversion factor between two compatible units. + + This function calculates the conversion factor between two units, specified + by their URIs or labels. If the provided units are not valid URLs, the + function attempts to check a QUDT (Quantities, Units, Dimensions, and Data + Types in OWL and XML) mapping to retrieve the correct URI. + + Parameters: + original_unit (str): The original unit to convert from, specified by URI or label. + target_unit (str): The target unit to convert to, specified by URI or label. + rounded (Optional[int]): An optional parameter specifying the number of + decimal places to round the conversion factor to. Default is None, + indicating no rounding. + + Returns: + float: The conversion factor from the original unit to the target unit. + + Raises: + ValueError: If the conversion factor cannot be determined due to invalid + unit specifications, missing mapping in QUDT, or if the units are not + compatible for conversion. + + Example: + >>> get_conversion_factor('http://qudt.org/vocab/unit/M', 'http://qudt.org/vocab/unit/IN') + 39.3701 + >>> get_conversion_factor('m', 'in') + 39.3701 + """ + if not _is_valid_url(original_unit): + original_unit = _check_qudt_mapping(original_unit) + if not _is_valid_url(target_unit): + target_unit = _check_qudt_mapping(target_unit) + if not _units_are_compatible(original_unit, target_unit): + raise ValueError( + f"Unit {original_unit} can numerically not be converted into {target_unit}" + ) + factor = _get_factor_from_uri(original_unit) / _get_factor_from_uri( + target_unit + ) + if rounded: + factor = round(factor, rounded) + return factor + + def _is_valid_url(url): try: result = urlparse(url) @@ -41,47 +90,34 @@ def _qudt_sparql_factor(uri: str) -> str: }}""" -@lru_cache -def _get_qudt_ontology() -> requests.Response: - from dsms import Context - - url = Context.dsms.config.qudt_uri - response = requests.get(url, timeout=Context.dsms.config.request_timeout) - if response.status_code != 200: - raise RuntimeError( - f"Could not download QUDT ontology. Please check URI: {url}" - ) - response.encoding = "utf-8" - return response - - -def _to_tempfile(content) -> str: - with tempfile.NamedTemporaryFile( - mode="w", suffix=".ttl", delete=False, encoding="utf-8" - ) as tmp: - tmp.write(content) - return tmp.name +def _qudt_sparql_quantity(original_uri: str, target_uri: str) -> str: + return f"""PREFIX qudt: + SELECT DISTINCT ?kind + WHERE {{ + ?kind a qudt:QuantityKind ; + qudt:applicableUnit <{original_uri}> , <{target_uri}> . + }}""" @lru_cache -def _get_qudt_graph() -> Graph: - response = _get_qudt_ontology() - file = _to_tempfile(response.text) - - graph = Graph() - graph.parse(file, encoding="utf-8") - return graph +def _units_are_compatible( + original_uri: str, target_uri: str +) -> Optional[bool]: + graph = _get_qudt_graph("qudt_quantity_kinds") + query = _qudt_sparql_quantity(original_uri, target_uri) + quantity = [str(row["kind"]) for row in graph.query(query)] + if len(quantity) == 0: + are_compatiable = False + else: + are_compatiable = True + return are_compatiable @lru_cache -def _get_query_match(symbol: str) -> List[str]: - graph = _get_qudt_graph() - query = _qudt_sparql(symbol) - return [str(row["unit"]) for row in graph.query(query)] - - def _check_qudt_mapping(symbol: str) -> Optional[str]: - match = _get_query_match(symbol) + graph = _get_qudt_graph("qudt_units") + query = _qudt_sparql(symbol) + match = [str(row["unit"]) for row in graph.query(query)] if len(match) == 0: raise ValueError( f"No QUDT Mapping found for unit with symbol `{symbol}`." @@ -93,45 +129,9 @@ def _check_qudt_mapping(symbol: str) -> Optional[str]: return match.pop() -@lru_cache -def get_conversion_factor(original_unit: str, target_unit: str) -> float: - """ - Calculate the conversion factor between two units. - - This function calculates the conversion factor between two units, specified - by their URIs or labels. If the provided units are not valid URLs, the - function attempts to check a QUDT (Quantities, Units, Dimensions, and Data - Types in OWL and XML) mapping to retrieve the correct URI. - - Parameters: - original_unit (str): The original unit to convert from, specified by URI or unit symbol. - target_unit (str): The target unit to convert to, specified by URI or label. - - Returns: - float: The conversion factor from the original unit to the target unit. - - Raises: - ValueError: If the conversion factor cannot be determined due to invalid - unit specifications or missing mapping in QUDT. - - Example: - >>> get_conversion_factor('http://qudt.org/vocab/unit/M', 'http://qudt.org/vocab/unit/IN') - 39.3701 - >>> get_conversion_factor('m', 'in') - 39.3701 - """ - if not _is_valid_url(original_unit): - original_unit = _check_qudt_mapping(original_unit) - if not _is_valid_url(target_unit): - target_unit = _check_qudt_mapping(target_unit) - return _get_factor_from_uri(original_unit) / _get_factor_from_uri( - target_unit - ) - - @lru_cache def _get_factor_from_uri(uri: str) -> int: - graph = _get_qudt_graph() + graph = _get_qudt_graph("qudt_units") query = _qudt_sparql_factor(uri) factor = [float(row["factor"]) for row in graph.query(query)] if len(factor) == 0: @@ -141,3 +141,26 @@ def _get_factor_from_uri(uri: str) -> int: f"More than one conversion factor for unit with uri `{uri}`." ) return factor.pop() + + +@lru_cache +def _get_qudt_graph(ontology_ref: str) -> Graph: + from dsms import Context + + url = getattr(Context.dsms.config, ontology_ref) + encoding = Context.dsms.config.encoding + graph = Graph() + + response = requests.get(url, timeout=Context.dsms.config.request_timeout) + if response.status_code != 200: + raise RuntimeError( + f"Could not download QUDT ontology. Please check URI: {url}" + ) + response.encoding = encoding + + with StringIO() as tmp: + tmp.write(response.text) + tmp.seek(0) + graph.parse(tmp, encoding=encoding) + + return graph diff --git a/examples/get_units.py b/examples/get_units.py new file mode 100644 index 0000000..60f2740 --- /dev/null +++ b/examples/get_units.py @@ -0,0 +1,37 @@ +"""DSMS unit conversion example""" + +import os + +from dotenv import load_dotenv + +from dsms import DSMS +from dsms.knowledge.semantics.units import get_conversion_factor + +env = os.path.join("..", ".env") + +load_dotenv(env) + +dsms = DSMS() + +# Meters to millimeters +print(get_conversion_factor("m", "mm")) + +# Kilometers to Inches, we can also round the factor in decimal places +print(get_conversion_factor("km", "in", rounded=1)) + +# GigaPascal to MegaPascal +print(get_conversion_factor("GPa", "MPa")) + +# we can also use the qudt iris +print( + get_conversion_factor( + "http://qudt.org/vocab/unit/M", "http://qudt.org/vocab/unit/IN" + ) +) + +# this will raise an error because the units are not compatible +try: + # Kilopascal to Centimeter + get_conversion_factor("kPa", "cm") +except ValueError as error: + print(error.args) diff --git a/tests/conftest.py b/tests/conftest.py index 1454475..fe4b533 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -71,6 +71,8 @@ def passthru() -> "List[str]": return [ "https://qudt.org/2.1/vocab/unit", "http://qudt.org/2.1/vocab/unit", + "http://qudt.org/vocab/quantitykind", + "https://qudt.org/vocab/quantitykind", ] diff --git a/tests/test_utils.py b/tests/test_utils.py index 2a00cbd..b2d531d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -120,8 +120,20 @@ def test_unit_conversion(custom_address): with pytest.warns(UserWarning, match="No authentication details"): DSMS(host_url=custom_address) - assert round(get_conversion_factor("mm", "m"), 3) == 0.001 + assert get_conversion_factor("mm", "m", rounded=3) == 0.001 - assert round(get_conversion_factor("km", "in"), 1) == 39370.1 + assert get_conversion_factor("km", "in", rounded=1) == 39370.1 - assert round(get_conversion_factor("GPa", "MPa")) == 1000 + assert get_conversion_factor("GPa", "MPa") == 1000 + + assert ( + get_conversion_factor( + "http://qudt.org/vocab/unit/M", + "http://qudt.org/vocab/unit/IN", + rounded=1, + ) + == 39.4 + ) + + with pytest.raises(ValueError, match="Unit "): + get_conversion_factor("kPa", "cm") From e93472d809f22b168393b0d88aabb6eee7657ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 5 Apr 2024 17:46:11 +0200 Subject: [PATCH 003/114] update example, add notebook and more complicated example --- examples/get_units.py | 37 ------ examples/unit_conversion.ipynb | 230 +++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 37 deletions(-) delete mode 100644 examples/get_units.py create mode 100644 examples/unit_conversion.ipynb diff --git a/examples/get_units.py b/examples/get_units.py deleted file mode 100644 index 60f2740..0000000 --- a/examples/get_units.py +++ /dev/null @@ -1,37 +0,0 @@ -"""DSMS unit conversion example""" - -import os - -from dotenv import load_dotenv - -from dsms import DSMS -from dsms.knowledge.semantics.units import get_conversion_factor - -env = os.path.join("..", ".env") - -load_dotenv(env) - -dsms = DSMS() - -# Meters to millimeters -print(get_conversion_factor("m", "mm")) - -# Kilometers to Inches, we can also round the factor in decimal places -print(get_conversion_factor("km", "in", rounded=1)) - -# GigaPascal to MegaPascal -print(get_conversion_factor("GPa", "MPa")) - -# we can also use the qudt iris -print( - get_conversion_factor( - "http://qudt.org/vocab/unit/M", "http://qudt.org/vocab/unit/IN" - ) -) - -# this will raise an error because the units are not compatible -try: - # Kilopascal to Centimeter - get_conversion_factor("kPa", "cm") -except ValueError as error: - print(error.args) diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb new file mode 100644 index 0000000..08b0f97 --- /dev/null +++ b/examples/unit_conversion.ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# DSMS SDK unit conversion example " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS\n", + "from dsms.knowledge.semantics.units import get_conversion_factor\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start session" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Meters to millimeters" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1000.0" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_conversion_factor(\"m\", \"mm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Kilometers to Inches, we can also round the factor in decimal places" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "39370.1" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_conversion_factor(\"km\", \"in\", rounded=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "GigaPascal to MegaPascal" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1000.0" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_conversion_factor(\"GPa\", \"MPa\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "we can also use the qudt iris" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "39.37007874015748" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_conversion_factor(\n", + " \"http://qudt.org/vocab/unit/M\", \"http://qudt.org/vocab/unit/IN\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Kilopascal to Centimeter - this will raise an error because the units are not compatible" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Unit http://qudt.org/vocab/unit/KiloPA can numerically not be converted into http://qudt.org/vocab/unit/CentiM\n" + ] + } + ], + "source": [ + "try:\n", + " get_conversion_factor(\"kPa\", \"cm\")\n", + "except ValueError as error:\n", + " print(error.args[0])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Something more complicated: Micromoles per litre day into Reciprocal square metre per second" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.15740740740741e-08" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_conversion_factor(\"µm/(L·day)\", \"/(m²·s)\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 2392232a4e4dd16e20d1c3939e7a60e5d31cb64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 5 Apr 2024 17:49:10 +0200 Subject: [PATCH 004/114] update example notebook --- examples/unit_conversion.ipynb | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 08b0f97..3d3d86c 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -25,12 +25,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Start session" + "### Start session" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -45,12 +45,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Meters to millimeters" + "### Meters to millimeters" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -59,7 +59,7 @@ "1000.0" ] }, - "execution_count": 11, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -72,12 +72,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Kilometers to Inches, we can also round the factor in decimal places" + "### Kilometers to Inches: we can also round the factor in decimal places" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -86,7 +86,7 @@ "39370.1" ] }, - "execution_count": 12, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -99,12 +99,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "GigaPascal to MegaPascal" + "### GigaPascal to MegaPascal" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -113,7 +113,7 @@ "1000.0" ] }, - "execution_count": 13, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -126,28 +126,28 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "we can also use the qudt iris" + "### We can also use the qudt IRIs" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "39.37007874015748" + "39.37" ] }, - "execution_count": 14, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_conversion_factor(\n", - " \"http://qudt.org/vocab/unit/M\", \"http://qudt.org/vocab/unit/IN\"\n", + " \"http://qudt.org/vocab/unit/M\", \"http://qudt.org/vocab/unit/IN\", rounded=2\n", ")" ] }, @@ -155,12 +155,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Kilopascal to Centimeter - this will raise an error because the units are not compatible" + "### KiloPascal to Centimeter: this will raise an error because the units are not compatible" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -182,12 +182,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Something more complicated: Micromoles per litre day into Reciprocal square metre per second" + "### Something more complicated: Micromoles per litre day into Reciprocal square metre per second" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -196,7 +196,7 @@ "1.15740740740741e-08" ] }, - "execution_count": 16, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } From 36e17b099b86fd75da6a16493f4f19f6b44d88ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 5 Apr 2024 17:53:44 +0200 Subject: [PATCH 005/114] update example --- examples/unit_conversion.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 3d3d86c..c30af02 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -182,12 +182,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Something more complicated: Micromoles per litre day into Reciprocal square metre per second" + "### Something more complicated: Micromoles per litre day into Reciprocal Second Square Meter" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -196,13 +196,13 @@ "1.15740740740741e-08" ] }, - "execution_count": 25, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_conversion_factor(\"µm/(L·day)\", \"/(m²·s)\")" + "get_conversion_factor(\"µm/(L·day)\", \"s⁻¹·m⁻²\")" ] } ], From 9ab2f27f45b175d2ac0a0f4ab5e520fb9680a225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 8 Apr 2024 17:49:38 +0200 Subject: [PATCH 006/114] add semantic bundle for units --- dsms/core/attribute_dict.py | 71 ++ dsms/core/entrypoints.py | 71 ++ dsms/knowledge/properties/hdf5.py | 30 +- dsms/knowledge/semantics/queries/__init__.py | 5 + dsms/knowledge/semantics/queries/base.py | 65 + dsms/knowledge/semantics/units/__init__.py | 8 + dsms/knowledge/semantics/units/base.py | 35 + .../{units.py => units/conversion.py} | 51 +- dsms/knowledge/semantics/units/sparql.py | 75 ++ dsms/knowledge/semantics/units/utils.py | 87 ++ examples/unit_conversion.ipynb | 1060 ++++++++++++++++- 11 files changed, 1492 insertions(+), 66 deletions(-) create mode 100644 dsms/core/attribute_dict.py create mode 100644 dsms/core/entrypoints.py create mode 100644 dsms/knowledge/semantics/queries/__init__.py create mode 100644 dsms/knowledge/semantics/queries/base.py create mode 100644 dsms/knowledge/semantics/units/__init__.py create mode 100644 dsms/knowledge/semantics/units/base.py rename dsms/knowledge/semantics/{units.py => units/conversion.py} (62%) create mode 100644 dsms/knowledge/semantics/units/sparql.py create mode 100644 dsms/knowledge/semantics/units/utils.py diff --git a/dsms/core/attribute_dict.py b/dsms/core/attribute_dict.py new file mode 100644 index 0000000..1c4f1e7 --- /dev/null +++ b/dsms/core/attribute_dict.py @@ -0,0 +1,71 @@ +"""DSMS module for immutable dynamic attribute dictionary.""" +from typing import Any + + +class ImmutableDynamicAttributesDict: + """Generic dictionary which sets items as + attributes when assigned and stays immutable + once instanciated.""" + + _immutable = False + + @property + def __dict__(cls): + return cls._data + + def __init__(self, **kwargs: Any): + self._data = {**kwargs} + self._immutable = True + + def _check_immutable(self): + if self._immutable: + raise AttributeError(f"{self} is immutable.") + + def __setattr__(self, key, value): + self._check_immutable() + if key != "_data": + self._data[key] = value + super().__setattr__(key, value) + + def __getattr__(self, key): + if key in self._data: + return self._data[key] + raise AttributeError(f"{self} has no attribute '{key}'") + + def __getitem__(self, key): + return self._data.get(key, None) + + def __setitem__(self, key, value): + self._check_immutable() + self._data[key] = value + + def __delitem__(self, key): + self._check_immutable() + del self._data[key] + + def _get_items(self): + return { + key: (list(value) if not callable(value) else value) + for key, value in self._data.items() + if key != "_immutable" + } + + def __iter__(self): + data = self._get_items() + yield from data.items() + + def get(self, key: Any) -> Any: + """Get value for key.""" + return self[key] + + def __str__(self): + items = ", ".join( + f"{key}={value}" + for key, value in self._data.items() + if key != "_immutable" + ) + return f"{self.__class__.__name__}({items})" + + +class LoaderResult(ImmutableDynamicAttributesDict): + """Immutalbe Dynamic Attribute Dict for result from loaders""" diff --git a/dsms/core/entrypoints.py b/dsms/core/entrypoints.py new file mode 100644 index 0000000..e747df8 --- /dev/null +++ b/dsms/core/entrypoints.py @@ -0,0 +1,71 @@ +"""DSMS Module defining entrypoints""" +from enum import Enum +import sys +from importlib.metadata import entry_points +from typing import Callable, Dict, Iterable, Union + +from dsms.core.attribute_dict import ImmutableDynamicAttributesDict +from dsms.core.entrypoints import DSMSEntrypoints + + +class DSMSEntrypoints(str, Enum): + """Enum class defining DSMS entrypoints.""" + + SPARQL = "dsms.sparql" + + + +def _get_entrypoints(entrypoint_group: DSMSEntrypoints) -> Iterable: + if not isinstance(entrypoint_group, DSMSEntrypoints): + raise TypeError( + f"""Positional argument for `entrypoint_group` + must be of type {DSMSEntrypoints}, not {type(entrypoint_group)}""" + ) + package_entry_points = entry_points() + if sys.version_info >= (3, 10): + return package_entry_points.select(group=entrypoint_group.value) + return package_entry_points.get(entrypoint_group.value, tuple()) + + +def load_entrypoints( + entrypoint_group: DSMSEntrypoints, names_only: bool = True +) -> Dict[str, Union[str, Callable]]: + """Get dictionary for a specific group of available entry points.""" + return { + entry_point.name: ( + entry_point.name if names_only else entry_point.load() + ) + for entry_point in _get_entrypoints(entrypoint_group) + } + + +def get_entrypoint(name: str, entrypoint_group: DSMSEntrypoints) -> Callable: + """Load a specific class from an entrypoint.""" + plugins = load_entrypoints(entrypoint_group, names_only=False) + if name not in plugins: + raise AttributeError(name) + return plugins[name] + + +def make_attribute_dict( + entrypoint_group: DSMSEntrypoints, + names_only: bool = True, +) -> ImmutableDynamicAttributesDict: + """Make entrypoints for immutable dynamic attribute dict.""" + return ImmutableDynamicAttributesDict( + **load_entrypoints(entrypoint_group, names_only=names_only) + ) + + +class Registry(ImmutableDynamicAttributesDict): + """DSMS Registry for enums of types plugins, mappers, queries, etc. + In order to avoid circular import, the kwargs `load` during initialization + is set to `False`. If set to `True`, the values of the attributes resolve + into callbables instead of strings with the names.""" + + def __init__(self, load=False) -> None: + data = { + obj.name.lower(): make_attribute_dict(obj, names_only=not load) + for obj in list(DSMSEntrypoints) + } + super().__init__(**data) diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index ef634d9..4994896 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -1,14 +1,16 @@ """HDF5 Properties of a KItem""" from typing import TYPE_CHECKING +import numpy as np import pandas as pd from pydantic import Field from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.semantics.units import get_conversion_factor, get_property_unit from dsms.knowledge.utils import _get_hdf5_column if TYPE_CHECKING: - from typing import Any, Callable, List + from typing import Any, Callable, Dict, List, Optional class Column(KPropertyItem): @@ -24,6 +26,26 @@ def get(self) -> "List[Any]": """Download the data for the column in a time series""" return _get_hdf5_column(self.id, self.column_id) + def get_unit(self) -> "Dict[str, Any]": + return get_property_unit(self.id, self.name, is_hdf5_column=True) + + def convert_to( + self, + unit_symbol_or_iri: str, + decimals: "Optional[int]" = None, + use_input_iri: bool = False, + ) -> "List[Any]": + unit = self.get_unit() + if use_input_iri: + input_str = unit.get("iri") + else: + input_str = unit.get("symbol") + factor = get_conversion_factor( + input_str, unit_symbol_or_iri, decimals=decimals + ) + data = self.get() + return list(np.array(data) * factor) + class HDF5Container(KProperty): """HDF5 container of a data frame related to a KItem""" @@ -56,3 +78,9 @@ def to_df(self) -> pd.DataFrame: """Return hdf5 as pandas DataFrame""" data = {column.name: column.get() for column in self} return pd.DataFrame.from_dict(data) + + def get(self, name: str) -> "Optional[Column]": + """Get a column with a certain name.""" + for column in self: + if column.name == name: + return column diff --git a/dsms/knowledge/semantics/queries/__init__.py b/dsms/knowledge/semantics/queries/__init__.py new file mode 100644 index 0000000..3eab0fe --- /dev/null +++ b/dsms/knowledge/semantics/queries/__init__.py @@ -0,0 +1,5 @@ +"""DSMS Semantic Queries""" + +from .base import BaseSparqlQuery + +__all__ = ["BaseSparqlQuery"] \ No newline at end of file diff --git a/dsms/knowledge/semantics/queries/base.py b/dsms/knowledge/semantics/queries/base.py new file mode 100644 index 0000000..09b6533 --- /dev/null +++ b/dsms/knowledge/semantics/queries/base.py @@ -0,0 +1,65 @@ +"""DSMS Base Abstract Class for Queries""" + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +from rdflib.plugins.sparql.results.jsonresults import JSONResult + +if TYPE_CHECKING: + from typing import Any, Dict, Optional + from dsms import DSMS + +class BaseSparqlQuery(ABC): + """Abstract class for DSMS Sparql Query""" + + def __init__(self, **kwargs: "Dict[str, Any]") -> None: + from dsms import Context + + self._kwargs = kwargs + self._results: "Optional[Dict[str, Any]]" = None + self._dsms: "DSMS" = Context.dsms + + self.execute() + + @property + def kwargs(cls) -> "Dict[str, Any]": + """Return kwargs passed during initialization""" + return cls._kwargs + + @property + def dsms(cls) -> "Dict[str, Any]": + """Return dsms context""" + return cls._dsms + + @property + def results(cls) -> "Optional[Dict[str, Any]]": + """Return query results""" + return cls._results + + @property + @abstractmethod + def result_mappings(self) -> "Dict[str, Any]": + """ + Define a mapping between the output keys and the output datatype. + E.g. {'foo': int, 'bar': str, 'foobar': MyCustomClass} + """ + + @property + @abstractmethod + def query(cls) -> str: + """ + Define sparql query by using the kwargs defined during initialization. + """ + + def execute(self) -> None: + """Execute sparql query and bind results.""" + result = self.dsms.sparql_interface.query(self.query) + self._results = [] + for row in JSONResult(result).bindings: + row_converted = {str(key): value for key, value in row.items()} + self._results.append( + { + name: func(row_converted.get(name)) + for name, func in self.result_mappings.items() + } + ) diff --git a/dsms/knowledge/semantics/units/__init__.py b/dsms/knowledge/semantics/units/__init__.py new file mode 100644 index 0000000..909ac94 --- /dev/null +++ b/dsms/knowledge/semantics/units/__init__.py @@ -0,0 +1,8 @@ +"""Semantic units module of the DSMS""" + +from .utils import get_conversion_factor, get_property_unit + +__all__ = [ + "get_conversion_factor", + "get_property_unit" +] diff --git a/dsms/knowledge/semantics/units/base.py b/dsms/knowledge/semantics/units/base.py new file mode 100644 index 0000000..07e94b8 --- /dev/null +++ b/dsms/knowledge/semantics/units/base.py @@ -0,0 +1,35 @@ +"""DSMS acstract class for units sparql query""" + +from dsms.knowledge.semantics.queries import BaseSparqlQuery +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + + from typing import Union, Dict, Any + from uuid import UUID + + +class BaseUnitSparqlQuery(BaseSparqlQuery): + """ + Abstract class for defining sparql queries fetching + the units of a hdf5 column or custom property of + a kitem. + """ + + def __init__(self, + kitem_id: "Union[str, UUID]", + property_name: str, + is_hdf5_column: bool = False) -> None: + super().__init__( + kitem_id=kitem_id, + property_name=property_name, + is_hdf5_column=is_hdf5_column) + + # OVERRIDE + @property + def result_mappings(cls) -> "Dict[str, Any]": + """Define mappings for the results of the units sparql queries""" + return { + "symbol": str, + "iri": str + } diff --git a/dsms/knowledge/semantics/units.py b/dsms/knowledge/semantics/units/conversion.py similarity index 62% rename from dsms/knowledge/semantics/units.py rename to dsms/knowledge/semantics/units/conversion.py index d6e964e..e6b92e7 100644 --- a/dsms/knowledge/semantics/units.py +++ b/dsms/knowledge/semantics/units/conversion.py @@ -9,56 +9,7 @@ from rdflib import Graph -@lru_cache -def get_conversion_factor( - original_unit: str, target_unit: str, rounded: Optional[int] = None -) -> float: - """ - Calculate the conversion factor between two compatible units. - - This function calculates the conversion factor between two units, specified - by their URIs or labels. If the provided units are not valid URLs, the - function attempts to check a QUDT (Quantities, Units, Dimensions, and Data - Types in OWL and XML) mapping to retrieve the correct URI. - - Parameters: - original_unit (str): The original unit to convert from, specified by URI or label. - target_unit (str): The target unit to convert to, specified by URI or label. - rounded (Optional[int]): An optional parameter specifying the number of - decimal places to round the conversion factor to. Default is None, - indicating no rounding. - - Returns: - float: The conversion factor from the original unit to the target unit. - - Raises: - ValueError: If the conversion factor cannot be determined due to invalid - unit specifications, missing mapping in QUDT, or if the units are not - compatible for conversion. - - Example: - >>> get_conversion_factor('http://qudt.org/vocab/unit/M', 'http://qudt.org/vocab/unit/IN') - 39.3701 - >>> get_conversion_factor('m', 'in') - 39.3701 - """ - if not _is_valid_url(original_unit): - original_unit = _check_qudt_mapping(original_unit) - if not _is_valid_url(target_unit): - target_unit = _check_qudt_mapping(target_unit) - if not _units_are_compatible(original_unit, target_unit): - raise ValueError( - f"Unit {original_unit} can numerically not be converted into {target_unit}" - ) - factor = _get_factor_from_uri(original_unit) / _get_factor_from_uri( - target_unit - ) - if rounded: - factor = round(factor, rounded) - return factor - - -def _is_valid_url(url): +def _is_valid_url(url: str) -> bool: try: result = urlparse(url) return all([result.scheme, result.netloc]) diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py new file mode 100644 index 0000000..f75f0ba --- /dev/null +++ b/dsms/knowledge/semantics/units/sparql.py @@ -0,0 +1,75 @@ +"""Semantic units for a custom property of a KItem in DSMS""" + +from dsms.knowledge.semantics.units.base import BaseUnitSparqlQuery +from urllib.parse import urljoin + +class UnitSparqlQuery(BaseUnitSparqlQuery): + """ + Define a sparql query for fetching the units of an HDf5 column of a KItem + """ + + @property + def query(cls) -> str: + """Construct sparql query for getting unit for hdf5 column""" + kitem_id = cls.kwargs.get("kitem_id") + property_name = cls.kwargs.get("property_name") + if not kitem_id: + raise ValueError("KItem ID must be defined.") + if not property_name: + raise ValueError("Property name must be defined.") + url = urljoin(str(cls.dsms.config.host_url), str(kitem_id)) + + if cls.kwargs.get("is_hdf5_column"): + query = f""" + prefix datamodel: + prefix metro: + prefix math: + prefix perceptual: + prefix reductionistic: + + select distinct + (STR(?symbol_literal) as ?symbol) + ?iri + where {{ + + <{url}/dataset> datamodel:composition ?prop . + + ?prop rdfs:label "{property_name}" ; + rdf:type datamodel:DataInstance ; + metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?literal, ?unit . + ?unit rdf:type ?iri . + ?literal a metro:UnitLiteral ; + metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?symbol_literal . + FILTER(?iri != metro:UnitLiteral) + }} + """ + + else: + query = f""" + prefix datamodel: + prefix metro: + prefix math: + prefix perceptual: + prefix reductionistic: + + select distinct + ?symbol + ?iri + where {{ + + <{url}/dataset> datamodel:composition ?prop . + + ?prop rdfs:label "{property_name}" ; + rdf:type datamodel:Metadata ; + metro:EMMO_8ef3cd6d_ae58_4a8d_9fc0_ad8f49015cd0 ?numeric . + + ?numeric rdf:type math:EMMO_4ce76d7f_03f8_45b6_9003_90052a79bfaa ; + metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?unit , ?literal . + + ?literal a metro:UnitLiteral ; + perceptual:EMMO_23b579e1_8088_45b5_9975_064014026c42 ?symbol . + + ?unit reductionistic:EMMO_b2282816_b7a3_44c6_b2cb_3feff1ceb7fe ?iri . + }} + """ + return query diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py new file mode 100644 index 0000000..6c42059 --- /dev/null +++ b/dsms/knowledge/semantics/units/utils.py @@ -0,0 +1,87 @@ +"""Utilities for unit semantics of a KItem""" + +from .conversion import _is_valid_url, _check_qudt_mapping, _units_are_compatible, _get_factor_from_uri +from .sparql import UnitSparqlQuery +from typing import TYPE_CHECKING +from functools import lru_cache + +if TYPE_CHECKING: + from typing import Optional, Dict, Any, Union + from uuid import UUID + +@lru_cache +def get_conversion_factor( + original_unit: str, target_unit: str, decimals: "Optional[int]" = None +) -> float: + """ + Calculate the conversion factor between two compatible units. + + This function calculates the conversion factor between two units, specified + by their URIs or labels. If the provided units are not valid URLs, the + function attempts to check a QUDT (Quantities, Units, Dimensions, and Data + Types in OWL and XML) mapping to retrieve the correct URI. + + Parameters: + original_unit (str): The original unit to convert from, specified by URI or label. + target_unit (str): The target unit to convert to, specified by URI or label. + decimals (Optional[int]): An optional parameter specifying the number of + decimal places to round the conversion factor to. Default is None, + indicating no rounding. + + Returns: + float: The conversion factor from the original unit to the target unit. + + Raises: + ValueError: If the conversion factor cannot be determined due to invalid + unit specifications, missing mapping in QUDT, or if the units are not + compatible for conversion. + + Example: + >>> get_conversion_factor('http://qudt.org/vocab/unit/M', 'http://qudt.org/vocab/unit/IN') + 39.3701 + >>> get_conversion_factor('m', 'in') + 39.3701 + """ + if not _is_valid_url(original_unit): + original_unit = _check_qudt_mapping(original_unit) + if not _is_valid_url(target_unit): + target_unit = _check_qudt_mapping(target_unit) + if not _units_are_compatible(original_unit, target_unit): + raise ValueError( + f"Unit {original_unit} can numerically not be converted into {target_unit}" + ) + factor = _get_factor_from_uri(original_unit) / _get_factor_from_uri( + target_unit + ) + if decimals: + factor = round(factor, decimals) + return factor + +def get_property_unit(kitem_id: "Union[str, UUID]", property_name: str, is_hdf5_column: bool = False) -> "Dict[str, Any]": + """ + Retrieve the unit associated with a given property of a KIitem. + + Args: + kitem (KItem): The identifier of the KItem. + property_name (str): The name of the property of the KItem for which + the unit is to be retrieved. + is_hdf5_column (bool, optional): Indicates whether the property is an HDF5 + column or a custom property. Defaults to False. + + Returns: + Dict[str, Any]: A dictionary with the symbol and iri of the unit associated + with the specified property. + + Raises: + ValueError: If unable to retrieve the unit for the property due to any errors or if + the property does not have a unit or has more than one unit associated with it. + """ + try: + query = UnitSparqlQuery(kitem_id, property_name, is_hdf5_column) + except Exception as error: + raise ValueError(f"Something went wrong catching the unit for property `{property_name}`.") from error + if len(query.results) == 0: + raise ValueError(f"Property `{property_name}` does not own any unit with respect to the semantics applied.") + elif len(query.results) > 1: + raise ValueError(f"Property `{property_name}` owns more than one unit with respect to the semantics applied.") + return query.results.pop() diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index c30af02..7eb1ecf 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -59,7 +59,7 @@ "1000.0" ] }, - "execution_count": 20, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -86,13 +86,13 @@ "39370.1" ] }, - "execution_count": 21, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_conversion_factor(\"km\", \"in\", rounded=1)" + "get_conversion_factor(\"km\", \"in\", decimals=1)" ] }, { @@ -104,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -113,7 +113,7 @@ "1000.0" ] }, - "execution_count": 22, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -131,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -140,14 +140,14 @@ "39.37" ] }, - "execution_count": 27, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_conversion_factor(\n", - " \"http://qudt.org/vocab/unit/M\", \"http://qudt.org/vocab/unit/IN\", rounded=2\n", + " \"http://qudt.org/vocab/unit/M\", \"http://qudt.org/vocab/unit/IN\", decimals=2\n", ")" ] }, @@ -160,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -187,7 +187,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -196,7 +196,7 @@ "1.15740740740741e-08" ] }, - "execution_count": 28, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -204,6 +204,1036 @@ "source": [ "get_conversion_factor(\"µm/(L·day)\", \"s⁻¹·m⁻²\")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also directly convert the columns of a HDF5 dataframe of a dataframe into a unit of interest." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.0819691,\n", + " 0.0820348,\n", + " 0.0820854,\n", + " 0.0820619,\n", + " 0.0820386,\n", + " 0.0820924,\n", + " 0.0821014,\n", + " 0.08210890000000001,\n", + " 0.0821103,\n", + " 0.0822442,\n", + " 0.0827859,\n", + " 0.08363190000000001,\n", + " 0.0846336,\n", + " 0.0856447,\n", + " 0.0866605,\n", + " 0.0876807,\n", + " 0.0886995,\n", + " 0.0897311,\n", + " 0.0907341,\n", + " 0.0917502,\n", + " 0.09278,\n", + " 0.09379269999999999,\n", + " 0.0948254,\n", + " 0.09582760000000001,\n", + " 0.096832,\n", + " 0.09786990000000001,\n", + " 0.0988997,\n", + " 0.0999365,\n", + " 0.100964,\n", + " 0.101998,\n", + " 0.10302800000000001,\n", + " 0.104029,\n", + " 0.105075,\n", + " 0.106114,\n", + " 0.10714,\n", + " 0.10819100000000001,\n", + " 0.109229,\n", + " 0.110281,\n", + " 0.111333,\n", + " 0.112383,\n", + " 0.113418,\n", + " 0.11445699999999999,\n", + " 0.11551399999999999,\n", + " 0.116562,\n", + " 0.11763,\n", + " 0.1187,\n", + " 0.119718,\n", + " 0.12075100000000001,\n", + " 0.12181600000000001,\n", + " 0.12288500000000001,\n", + " 0.123891,\n", + " 0.124923,\n", + " 0.125973,\n", + " 0.127044,\n", + " 0.12804400000000002,\n", + " 0.129065,\n", + " 0.130079,\n", + " 0.131119,\n", + " 0.13218000000000002,\n", + " 0.13324100000000003,\n", + " 0.134311,\n", + " 0.135313,\n", + " 0.13631800000000002,\n", + " 0.137338,\n", + " 0.13836099999999998,\n", + " 0.139367,\n", + " 0.140405,\n", + " 0.141445,\n", + " 0.14248,\n", + " 0.143527,\n", + " 0.144568,\n", + " 0.14561500000000002,\n", + " 0.14665799999999998,\n", + " 0.14772100000000002,\n", + " 0.148785,\n", + " 0.149847,\n", + " 0.150923,\n", + " 0.151993,\n", + " 0.153091,\n", + " 0.15418,\n", + " 0.15527000000000002,\n", + " 0.156367,\n", + " 0.15746700000000002,\n", + " 0.158492,\n", + " 0.159524,\n", + " 0.160537,\n", + " 0.161571,\n", + " 0.16262000000000001,\n", + " 0.16367400000000001,\n", + " 0.164722,\n", + " 0.165773,\n", + " 0.16685400000000003,\n", + " 0.167942,\n", + " 0.16902799999999998,\n", + " 0.170118,\n", + " 0.171206,\n", + " 0.172313,\n", + " 0.17341800000000002,\n", + " 0.174435,\n", + " 0.175474,\n", + " 0.17650100000000002,\n", + " 0.17753200000000002,\n", + " 0.178574,\n", + " 0.179602,\n", + " 0.180634,\n", + " 0.18166800000000002,\n", + " 0.18272300000000002,\n", + " 0.18377500000000002,\n", + " 0.18484,\n", + " 0.185898,\n", + " 0.18697,\n", + " 0.188062,\n", + " 0.189148,\n", + " 0.19023500000000002,\n", + " 0.19131700000000001,\n", + " 0.19243000000000002,\n", + " 0.193545,\n", + " 0.194664,\n", + " 0.195666,\n", + " 0.196791,\n", + " 0.197792,\n", + " 0.19891399999999998,\n", + " 0.199915,\n", + " 0.200917,\n", + " 0.202035,\n", + " 0.203046,\n", + " 0.204075,\n", + " 0.2051,\n", + " 0.206147,\n", + " 0.207202,\n", + " 0.208254,\n", + " 0.20929499999999998,\n", + " 0.21035800000000002,\n", + " 0.211423,\n", + " 0.212478,\n", + " 0.213535,\n", + " 0.214605,\n", + " 0.215678,\n", + " 0.216763,\n", + " 0.217838,\n", + " 0.21890600000000002,\n", + " 0.219975,\n", + " 0.221052,\n", + " 0.22215000000000001,\n", + " 0.223251,\n", + " 0.224351,\n", + " 0.225456,\n", + " 0.226578,\n", + " 0.227708,\n", + " 0.22871100000000003,\n", + " 0.229712,\n", + " 0.230716,\n", + " 0.231718,\n", + " 0.23286,\n", + " 0.233988,\n", + " 0.23512200000000003,\n", + " 0.236136,\n", + " 0.23715,\n", + " 0.238162,\n", + " 0.23919300000000002,\n", + " 0.240231,\n", + " 0.241262,\n", + " 0.242287,\n", + " 0.243316,\n", + " 0.244346,\n", + " 0.245377,\n", + " 0.246418,\n", + " 0.247471,\n", + " 0.248542,\n", + " 0.249613,\n", + " 0.250686,\n", + " 0.251741,\n", + " 0.252801,\n", + " 0.253871,\n", + " 0.254951,\n", + " 0.25602800000000003,\n", + " 0.25711700000000004,\n", + " 0.25819600000000004,\n", + " 0.259282,\n", + " 0.26036200000000004,\n", + " 0.261452,\n", + " 0.262552,\n", + " 0.263659,\n", + " 0.264779,\n", + " 0.265905,\n", + " 0.267023,\n", + " 0.26813200000000004,\n", + " 0.26924400000000004,\n", + " 0.270358,\n", + " 0.27148300000000003,\n", + " 0.27261900000000006,\n", + " 0.27376100000000003,\n", + " 0.27490499999999995,\n", + " 0.27606200000000003,\n", + " 0.277214,\n", + " 0.27835899999999997,\n", + " 0.27950400000000003,\n", + " 0.280652,\n", + " 0.2818,\n", + " 0.282957,\n", + " 0.283963,\n", + " 0.284974,\n", + " 0.28598,\n", + " 0.287146,\n", + " 0.288148,\n", + " 0.289151,\n", + " 0.290171,\n", + " 0.29119,\n", + " 0.29221199999999997,\n", + " 0.293236,\n", + " 0.294262,\n", + " 0.295286,\n", + " 0.296313,\n", + " 0.297333,\n", + " 0.298352,\n", + " 0.29936900000000005,\n", + " 0.300394,\n", + " 0.30143200000000003,\n", + " 0.30248,\n", + " 0.303524,\n", + " 0.304564,\n", + " 0.305611,\n", + " 0.30666000000000004,\n", + " 0.30770600000000004,\n", + " 0.308751,\n", + " 0.309799,\n", + " 0.310849,\n", + " 0.311906,\n", + " 0.31297,\n", + " 0.314039,\n", + " 0.315106,\n", + " 0.316166,\n", + " 0.317229,\n", + " 0.318288,\n", + " 0.319349,\n", + " 0.320409,\n", + " 0.321476,\n", + " 0.322549,\n", + " 0.32362,\n", + " 0.32469,\n", + " 0.325758,\n", + " 0.326818,\n", + " 0.32787900000000003,\n", + " 0.32895100000000005,\n", + " 0.330022,\n", + " 0.331087,\n", + " 0.33216,\n", + " 0.333237,\n", + " 0.334306,\n", + " 0.335384,\n", + " 0.336463,\n", + " 0.337534,\n", + " 0.338606,\n", + " 0.339687,\n", + " 0.340767,\n", + " 0.34185000000000004,\n", + " 0.342943,\n", + " 0.344037,\n", + " 0.345123,\n", + " 0.346212,\n", + " 0.34730500000000003,\n", + " 0.348402,\n", + " 0.349494,\n", + " 0.350592,\n", + " 0.35170100000000004,\n", + " 0.352801,\n", + " 0.35390499999999997,\n", + " 0.355011,\n", + " 0.356119,\n", + " 0.357233,\n", + " 0.35834699999999997,\n", + " 0.359462,\n", + " 0.360576,\n", + " 0.361702,\n", + " 0.362843,\n", + " 0.363991,\n", + " 0.365137,\n", + " 0.366287,\n", + " 0.367437,\n", + " 0.368587,\n", + " 0.369739,\n", + " 0.370897,\n", + " 0.37205200000000005,\n", + " 0.373214,\n", + " 0.374373,\n", + " 0.37553800000000004,\n", + " 0.376709,\n", + " 0.377877,\n", + " 0.379041,\n", + " 0.380214,\n", + " 0.38138299999999997,\n", + " 0.382556,\n", + " 0.38373399999999996,\n", + " 0.384919,\n", + " 0.386101,\n", + " 0.387281,\n", + " 0.388459,\n", + " 0.38963400000000004,\n", + " 0.390823,\n", + " 0.391824,\n", + " 0.392824,\n", + " 0.39382799999999996,\n", + " 0.39482799999999996,\n", + " 0.396023,\n", + " 0.397221,\n", + " 0.39842000000000005,\n", + " 0.39942,\n", + " 0.40061399999999997,\n", + " 0.401807,\n", + " 0.403003,\n", + " 0.404198,\n", + " 0.405384,\n", + " 0.40657400000000005,\n", + " 0.407575,\n", + " 0.408582,\n", + " 0.409597,\n", + " 0.41062200000000004,\n", + " 0.411643,\n", + " 0.412657,\n", + " 0.413677,\n", + " 0.41469900000000004,\n", + " 0.41571600000000003,\n", + " 0.41672800000000004,\n", + " 0.417742,\n", + " 0.41875799999999996,\n", + " 0.419772,\n", + " 0.420786,\n", + " 0.421801,\n", + " 0.422819,\n", + " 0.42384,\n", + " 0.424863,\n", + " 0.425894,\n", + " 0.426929,\n", + " 0.427968,\n", + " 0.429013,\n", + " 0.430059,\n", + " 0.431101,\n", + " 0.432139,\n", + " 0.433182,\n", + " 0.434225,\n", + " 0.435274,\n", + " 0.43632400000000005,\n", + " 0.437376,\n", + " 0.438433,\n", + " 0.4395,\n", + " 0.440574,\n", + " 0.441651,\n", + " 0.442724,\n", + " 0.44379,\n", + " 0.444853,\n", + " 0.445923,\n", + " 0.447003,\n", + " 0.44808800000000004,\n", + " 0.449168,\n", + " 0.450248,\n", + " 0.451325,\n", + " 0.452399,\n", + " 0.453483,\n", + " 0.45457600000000004,\n", + " 0.455667,\n", + " 0.45676,\n", + " 0.457857,\n", + " 0.458957,\n", + " 0.46005900000000005,\n", + " 0.461164,\n", + " 0.46227,\n", + " 0.463382,\n", + " 0.46449599999999996,\n", + " 0.465614,\n", + " 0.466729,\n", + " 0.467835,\n", + " 0.468944,\n", + " 0.470055,\n", + " 0.47116800000000003,\n", + " 0.47227600000000003,\n", + " 0.473382,\n", + " 0.474491,\n", + " 0.475601,\n", + " 0.476711,\n", + " 0.47782600000000003,\n", + " 0.47894600000000004,\n", + " 0.48007299999999997,\n", + " 0.481204,\n", + " 0.482331,\n", + " 0.48345,\n", + " 0.484559,\n", + " 0.485674,\n", + " 0.486798,\n", + " 0.487935,\n", + " 0.48907900000000004,\n", + " 0.49021800000000004,\n", + " 0.491359,\n", + " 0.492499,\n", + " 0.49363799999999997,\n", + " 0.49478,\n", + " 0.49592200000000003,\n", + " 0.497066,\n", + " 0.49821,\n", + " 0.499349,\n", + " 0.500486,\n", + " 0.501626,\n", + " 0.502774,\n", + " 0.503929,\n", + " 0.5050830000000001,\n", + " 0.506237,\n", + " 0.507391,\n", + " 0.508543,\n", + " 0.509699,\n", + " 0.5108550000000001,\n", + " 0.512009,\n", + " 0.51317,\n", + " 0.514334,\n", + " 0.5155,\n", + " 0.516663,\n", + " 0.5178360000000001,\n", + " 0.519009,\n", + " 0.520181,\n", + " 0.521361,\n", + " 0.522543,\n", + " 0.523721,\n", + " 0.5248980000000001,\n", + " 0.526073,\n", + " 0.527247,\n", + " 0.528424,\n", + " 0.52961,\n", + " 0.5307970000000001,\n", + " 0.531981,\n", + " 0.533164,\n", + " 0.53435,\n", + " 0.535537,\n", + " 0.536729,\n", + " 0.537928,\n", + " 0.539125,\n", + " 0.540325,\n", + " 0.5415209999999999,\n", + " 0.5427179999999999,\n", + " 0.543919,\n", + " 0.545122,\n", + " 0.546326,\n", + " 0.547535,\n", + " 0.548745,\n", + " 0.549952,\n", + " 0.55116,\n", + " 0.552377,\n", + " 0.553601,\n", + " 0.55483,\n", + " 0.55606,\n", + " 0.557289,\n", + " 0.558519,\n", + " 0.5597530000000001,\n", + " 0.5609890000000001,\n", + " 0.562219,\n", + " 0.5634560000000001,\n", + " 0.564699,\n", + " 0.565936,\n", + " 0.567177,\n", + " 0.568424,\n", + " 0.569667,\n", + " 0.570911,\n", + " 0.572155,\n", + " 0.573396,\n", + " 0.574641,\n", + " 0.575886,\n", + " 0.577126,\n", + " 0.57837,\n", + " 0.579615,\n", + " 0.580861,\n", + " 0.58211,\n", + " 0.5833579999999999,\n", + " 0.584605,\n", + " 0.585852,\n", + " 0.587091,\n", + " 0.5883339999999999,\n", + " 0.5893339999999999,\n", + " 0.590336,\n", + " 0.5913400000000001,\n", + " 0.592342,\n", + " 0.5933440000000001,\n", + " 0.5943440000000001,\n", + " 0.595346,\n", + " 0.596352,\n", + " 0.597364,\n", + " 0.598379,\n", + " 0.5993890000000001,\n", + " 0.6003970000000001,\n", + " 0.601401,\n", + " 0.602405,\n", + " 0.603414,\n", + " 0.60442,\n", + " 0.60542,\n", + " 0.606422,\n", + " 0.607428,\n", + " 0.6084360000000001,\n", + " 0.609448,\n", + " 0.610459,\n", + " 0.6114769999999999,\n", + " 0.612495,\n", + " 0.61351,\n", + " 0.6145280000000001,\n", + " 0.6155510000000001,\n", + " 0.616577,\n", + " 0.6176,\n", + " 0.618623,\n", + " 0.619649,\n", + " 0.620672,\n", + " 0.621698,\n", + " 0.6227229999999999,\n", + " 0.6237480000000001,\n", + " 0.6247780000000001,\n", + " 0.625811,\n", + " 0.626854,\n", + " 0.627893,\n", + " 0.628932,\n", + " 0.6299750000000001,\n", + " 0.631016,\n", + " 0.6320549999999999,\n", + " 0.63309,\n", + " 0.634129,\n", + " 0.63517,\n", + " 0.636213,\n", + " 0.6372580000000001,\n", + " 0.6383030000000001,\n", + " 0.6393500000000001,\n", + " 0.640389,\n", + " 0.641428,\n", + " 0.642473,\n", + " 0.64352,\n", + " 0.644569,\n", + " 0.64562,\n", + " 0.646672,\n", + " 0.647725,\n", + " 0.648768,\n", + " 0.649809,\n", + " 0.650852,\n", + " 0.651901,\n", + " 0.6529539999999999,\n", + " 0.654006,\n", + " 0.655057,\n", + " 0.656105,\n", + " 0.65716,\n", + " 0.6582210000000001,\n", + " 0.6592790000000001,\n", + " 0.66034,\n", + " 0.661398,\n", + " 0.6624589999999999,\n", + " 0.663519,\n", + " 0.6645800000000001,\n", + " 0.6656409999999999,\n", + " 0.6666989999999999,\n", + " 0.667752,\n", + " 0.668805,\n", + " 0.669861,\n", + " 0.670924,\n", + " 0.671986,\n", + " 0.6730430000000001,\n", + " 0.674103,\n", + " 0.6751699999999999,\n", + " 0.676234,\n", + " 0.677293,\n", + " 0.678348,\n", + " 0.679404,\n", + " 0.680471,\n", + " 0.6815410000000001,\n", + " 0.682611,\n", + " 0.683685,\n", + " 0.684762,\n", + " 0.685838,\n", + " 0.686914,\n", + " 0.687986,\n", + " 0.6890620000000001,\n", + " 0.690141,\n", + " 0.691221,\n", + " 0.692303,\n", + " 0.693389,\n", + " 0.694474,\n", + " 0.695557,\n", + " 0.696637,\n", + " 0.6977190000000001,\n", + " 0.698801,\n", + " 0.699883,\n", + " 0.7009690000000001,\n", + " 0.702055,\n", + " 0.703142,\n", + " 0.704236,\n", + " 0.70533,\n", + " 0.7064260000000001,\n", + " 0.707519,\n", + " 0.708613,\n", + " 0.7097129999999999,\n", + " 0.710818,\n", + " 0.71192,\n", + " 0.7130230000000001,\n", + " 0.714125,\n", + " 0.715228,\n", + " 0.71633,\n", + " 0.7174320000000001,\n", + " 0.718527,\n", + " 0.7196250000000001,\n", + " 0.720726,\n", + " 0.721836,\n", + " 0.722943,\n", + " 0.724047,\n", + " 0.725148,\n", + " 0.726256,\n", + " 0.727373,\n", + " 0.728492,\n", + " 0.729611,\n", + " 0.73073,\n", + " 0.731851,\n", + " 0.732974,\n", + " 0.734094,\n", + " 0.735207,\n", + " 0.7363200000000001,\n", + " 0.7374299999999999,\n", + " 0.7385410000000001,\n", + " 0.7396520000000001,\n", + " 0.7407670000000001,\n", + " 0.741887,\n", + " 0.7430059999999999,\n", + " 0.744121,\n", + " 0.745236,\n", + " 0.7463529999999999,\n", + " 0.747469,\n", + " 0.7485839999999999,\n", + " 0.749703,\n", + " 0.750832,\n", + " 0.7519710000000001,\n", + " 0.753105,\n", + " 0.754236,\n", + " 0.7553650000000001,\n", + " 0.756496,\n", + " 0.7576350000000001,\n", + " 0.758773,\n", + " 0.759914,\n", + " 0.761051,\n", + " 0.762189,\n", + " 0.7633260000000001,\n", + " 0.7644610000000001,\n", + " 0.76559,\n", + " 0.766721,\n", + " 0.7678550000000001,\n", + " 0.7689940000000001,\n", + " 0.770135,\n", + " 0.771279,\n", + " 0.7724260000000001,\n", + " 0.773572,\n", + " 0.774721,\n", + " 0.775873,\n", + " 0.777021,\n", + " 0.77817,\n", + " 0.779324,\n", + " 0.7804840000000001,\n", + " 0.781644,\n", + " 0.7828010000000001,\n", + " 0.7839550000000001,\n", + " 0.785107,\n", + " 0.786258,\n", + " 0.787412,\n", + " 0.7885700000000001,\n", + " 0.789732,\n", + " 0.7908959999999999,\n", + " 0.79206,\n", + " 0.7932279999999999,\n", + " 0.794396,\n", + " 0.795566,\n", + " 0.79674,\n", + " 0.797913,\n", + " 0.799085,\n", + " 0.800257,\n", + " 0.8014330000000001,\n", + " 0.8026150000000001,\n", + " 0.803794,\n", + " 0.804976,\n", + " 0.8061630000000001,\n", + " 0.807355,\n", + " 0.8085399999999999,\n", + " 0.8097179999999999,\n", + " 0.810898,\n", + " 0.8120850000000001,\n", + " 0.813277,\n", + " 0.814466,\n", + " 0.815652,\n", + " 0.8168390000000001,\n", + " 0.818035,\n", + " 0.819232,\n", + " 0.820429,\n", + " 0.8216319999999999,\n", + " 0.8228390000000001,\n", + " 0.82405,\n", + " 0.825257,\n", + " 0.826462,\n", + " 0.827671,\n", + " 0.8288780000000001,\n", + " 0.830083,\n", + " 0.831292,\n", + " 0.832505,\n", + " 0.83372,\n", + " 0.834937,\n", + " 0.836156,\n", + " 0.8373700000000001,\n", + " 0.838585,\n", + " 0.839808,\n", + " 0.841029,\n", + " 0.842245,\n", + " 0.843466,\n", + " 0.8446910000000001,\n", + " 0.845921,\n", + " 0.847152,\n", + " 0.848376,\n", + " 0.849597,\n", + " 0.850818,\n", + " 0.852036,\n", + " 0.8532569999999999,\n", + " 0.85448,\n", + " 0.855704,\n", + " 0.8569249999999999,\n", + " 0.858144,\n", + " 0.859367,\n", + " 0.860595,\n", + " 0.8618260000000001,\n", + " 0.8630599999999999,\n", + " 0.864294,\n", + " 0.865525,\n", + " 0.866751,\n", + " 0.8679800000000001,\n", + " 0.869206,\n", + " 0.870435,\n", + " 0.8716630000000001,\n", + " 0.87289,\n", + " 0.87412,\n", + " 0.8753569999999999,\n", + " 0.876599,\n", + " 0.877837,\n", + " 0.879068,\n", + " 0.880294,\n", + " 0.8815230000000001,\n", + " 0.882749,\n", + " 0.88398,\n", + " 0.8852140000000001,\n", + " 0.886445,\n", + " 0.887673,\n", + " 0.8889020000000001,\n", + " 0.89013,\n", + " 0.891359,\n", + " 0.892583,\n", + " 0.893812,\n", + " 0.895044,\n", + " 0.8962730000000001,\n", + " 0.897499,\n", + " 0.898732,\n", + " 0.8999600000000001,\n", + " 0.901183,\n", + " 0.902411,\n", + " 0.903648,\n", + " 0.904882,\n", + " 0.906109,\n", + " 0.9073289999999999,\n", + " 0.908552,\n", + " 0.909785,\n", + " 0.911025,\n", + " 0.912267,\n", + " 0.913511,\n", + " 0.9147569999999999,\n", + " 0.916001,\n", + " 0.917245,\n", + " 0.918494,\n", + " 0.919744,\n", + " 0.92099,\n", + " 0.9222340000000001,\n", + " 0.923476,\n", + " 0.924716,\n", + " 0.925956,\n", + " 0.927203,\n", + " 0.928454,\n", + " 0.929706,\n", + " 0.930954,\n", + " 0.931892,\n", + " 0.933146,\n", + " 0.934396,\n", + " 0.9356420000000001,\n", + " 0.936886,\n", + " 0.9381280000000001,\n", + " 0.939369,\n", + " 0.940615,\n", + " 0.9418630000000001,\n", + " 0.943111,\n", + " 0.944357,\n", + " 0.945603,\n", + " 0.946847,\n", + " 0.948097,\n", + " 0.9493510000000001,\n", + " 0.950603,\n", + " 0.951851,\n", + " 0.953101,\n", + " 0.954357,\n", + " 0.955613,\n", + " 0.9568650000000001,\n", + " 0.958122,\n", + " 0.959382,\n", + " 0.9606380000000001,\n", + " 0.961896,\n", + " 0.963156,\n", + " 0.9644170000000001,\n", + " 0.965675,\n", + " 0.966933,\n", + " 0.968197,\n", + " 0.969458,\n", + " 0.970724,\n", + " 0.971992,\n", + " 0.973259,\n", + " 0.974527,\n", + " 0.975794,\n", + " 0.977062,\n", + " 0.9783310000000001,\n", + " 0.979607,\n", + " 0.9808840000000001,\n", + " 0.982159,\n", + " 0.983433,\n", + " 0.984708,\n", + " 0.985986,\n", + " 0.9872650000000001,\n", + " 0.9885499999999999,\n", + " 0.989831,\n", + " 0.991111,\n", + " 0.99239,\n", + " 0.993669,\n", + " 0.994951,\n", + " 0.996236,\n", + " 0.997527,\n", + " 0.99882,\n", + " 1.00011,\n", + " 1.0014,\n", + " 1.00269,\n", + " 1.00398,\n", + " 1.00527,\n", + " 1.00657,\n", + " 1.00787,\n", + " 1.00917,\n", + " 1.01047,\n", + " 1.01177,\n", + " 1.0130700000000001,\n", + " 1.01437,\n", + " 1.01567,\n", + " 1.0169700000000002,\n", + " 1.01828,\n", + " 1.01959,\n", + " 1.02089,\n", + " 1.0221900000000002,\n", + " 1.0235,\n", + " 1.0248,\n", + " 1.0261099999999999,\n", + " 1.02742,\n", + " 1.0287300000000001,\n", + " 1.03004,\n", + " 1.0313599999999998,\n", + " 1.0326700000000002,\n", + " 1.0339800000000001,\n", + " 1.03529,\n", + " 1.03661,\n", + " 1.03793,\n", + " 1.03924,\n", + " 1.04055,\n", + " 1.0418699999999999,\n", + " 1.04318,\n", + " 1.0445,\n", + " 1.04581,\n", + " 1.0471300000000001,\n", + " 1.04845,\n", + " 1.04977,\n", + " 1.0510899999999999,\n", + " 1.05241,\n", + " 1.05372,\n", + " 1.05504,\n", + " 1.0563699999999998,\n", + " 1.0577,\n", + " 1.05902,\n", + " 1.06035,\n", + " 1.0616700000000001,\n", + " 1.06298,\n", + " 1.0643,\n", + " 1.06562,\n", + " 1.06695,\n", + " 1.06828,\n", + " 1.06928,\n", + " 1.07028,\n", + " 1.07128,\n", + " 1.07229,\n", + " 1.07329,\n", + " 1.07462,\n", + " 1.07562,\n", + " 1.0766300000000002,\n", + " 1.07763,\n", + " 1.0786300000000002,\n", + " 1.0796400000000002,\n", + " 1.08064,\n", + " 1.0816400000000002,\n", + " 1.0826500000000001,\n", + " 1.0836500000000002,\n", + " 1.0846600000000002,\n", + " 1.08566,\n", + " 1.08667,\n", + " 1.0876700000000001,\n", + " 1.08868,\n", + " 1.08969,\n", + " 1.0907,\n", + " 1.09172,\n", + " 1.09273,\n", + " 1.09374,\n", + " 1.09476,\n", + " 1.09577,\n", + " 1.09678,\n", + " 1.0977999999999999,\n", + " 1.09882,\n", + " 1.09985,\n", + " 1.10087,\n", + " 1.10189,\n", + " 1.10291,\n", + " 1.10393,\n", + " 1.10494,\n", + " 1.10596,\n", + " 1.10698,\n", + " 1.108,\n", + " 1.10901,\n", + " 1.11003,\n", + " 1.1110499999999999,\n", + " 1.11207,\n", + " 1.1130799999999998,\n", + " 1.11409,\n", + " 1.1151099999999998,\n", + " 1.11612,\n", + " 1.11714,\n", + " 1.11816,\n", + " 1.11917,\n", + " 1.12019,\n", + " 1.1212,\n", + " 1.1222100000000002,\n", + " 1.12321,\n", + " 1.12422,\n", + " 1.12523,\n", + " 1.1262400000000001,\n", + " 1.12726,\n", + " 1.12828,\n", + " 1.1293,\n", + " 1.13032,\n", + " 1.13133,\n", + " 1.13235,\n", + " 1.13337,\n", + " 1.1343900000000002,\n", + " 1.13541,\n", + " 1.13643,\n", + " 1.13745,\n", + " 1.13846,\n", + " 1.13948,\n", + " 1.1405,\n", + " 1.14152,\n", + " 1.14254,\n", + " 1.14356,\n", + " 1.14458,\n", + " 1.1456,\n", + " 1.14663,\n", + " 1.1476600000000001,\n", + " 1.14869,\n", + " 1.14972,\n", + " 1.15074,\n", + " 1.1517600000000001,\n", + " 1.15279,\n", + " 1.15382,\n", + " 1.15485,\n", + " 1.1558800000000002,\n", + " 1.15691,\n", + " 1.1579300000000001,\n", + " 1.15896,\n", + " 1.15999,\n", + " 1.16102,\n", + " 1.16205,\n", + " 1.16308,\n", + " 1.1641,\n", + " 1.1651300000000002,\n", + " 1.16616,\n", + " 1.16718,\n", + " 1.1682000000000001,\n", + " 1.1692200000000001,\n", + " 1.17025,\n", + " 1.17127,\n", + " 1.17231,\n", + " 1.17334,\n", + " 1.1743800000000002,\n", + " 1.17541,\n", + " 1.17645,\n", + " 1.17748,\n", + " 1.17851,\n", + " 1.1795499999999999,\n", + " 1.18059,\n", + " 1.18162,\n", + " 1.18266,\n", + " 1.1837,\n", + " 1.1847400000000001,\n", + " 1.18578,\n", + " 1.18682,\n", + " 1.18787,\n", + " ...]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "uuid = \"e49f1e17-f62d-46b2-b23f-72f1642a26ce\"\n", + "item = dsms[uuid]\n", + "\n", + "item.hdf5.get(\"Standardkraft\").convert_to(\"kN\")" + ] } ], "metadata": { From 64d7b12f9c04f82e83666c38a897e9e5c515bff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 9 Apr 2024 10:51:54 +0200 Subject: [PATCH 007/114] fix hooks and pytests --- dsms/core/attribute_dict.py | 71 -------------------- dsms/core/entrypoints.py | 71 -------------------- dsms/knowledge/properties/hdf5.py | 42 ++++++++++-- dsms/knowledge/semantics/queries/__init__.py | 2 +- dsms/knowledge/semantics/queries/base.py | 8 ++- dsms/knowledge/semantics/units/__init__.py | 5 +- dsms/knowledge/semantics/units/base.py | 24 +++---- dsms/knowledge/semantics/units/sparql.py | 16 +++-- dsms/knowledge/semantics/units/utils.py | 40 ++++++++--- examples/unit_conversion.ipynb | 43 +++++++----- tests/test_utils.py | 6 +- 11 files changed, 127 insertions(+), 201 deletions(-) delete mode 100644 dsms/core/attribute_dict.py delete mode 100644 dsms/core/entrypoints.py diff --git a/dsms/core/attribute_dict.py b/dsms/core/attribute_dict.py deleted file mode 100644 index 1c4f1e7..0000000 --- a/dsms/core/attribute_dict.py +++ /dev/null @@ -1,71 +0,0 @@ -"""DSMS module for immutable dynamic attribute dictionary.""" -from typing import Any - - -class ImmutableDynamicAttributesDict: - """Generic dictionary which sets items as - attributes when assigned and stays immutable - once instanciated.""" - - _immutable = False - - @property - def __dict__(cls): - return cls._data - - def __init__(self, **kwargs: Any): - self._data = {**kwargs} - self._immutable = True - - def _check_immutable(self): - if self._immutable: - raise AttributeError(f"{self} is immutable.") - - def __setattr__(self, key, value): - self._check_immutable() - if key != "_data": - self._data[key] = value - super().__setattr__(key, value) - - def __getattr__(self, key): - if key in self._data: - return self._data[key] - raise AttributeError(f"{self} has no attribute '{key}'") - - def __getitem__(self, key): - return self._data.get(key, None) - - def __setitem__(self, key, value): - self._check_immutable() - self._data[key] = value - - def __delitem__(self, key): - self._check_immutable() - del self._data[key] - - def _get_items(self): - return { - key: (list(value) if not callable(value) else value) - for key, value in self._data.items() - if key != "_immutable" - } - - def __iter__(self): - data = self._get_items() - yield from data.items() - - def get(self, key: Any) -> Any: - """Get value for key.""" - return self[key] - - def __str__(self): - items = ", ".join( - f"{key}={value}" - for key, value in self._data.items() - if key != "_immutable" - ) - return f"{self.__class__.__name__}({items})" - - -class LoaderResult(ImmutableDynamicAttributesDict): - """Immutalbe Dynamic Attribute Dict for result from loaders""" diff --git a/dsms/core/entrypoints.py b/dsms/core/entrypoints.py deleted file mode 100644 index e747df8..0000000 --- a/dsms/core/entrypoints.py +++ /dev/null @@ -1,71 +0,0 @@ -"""DSMS Module defining entrypoints""" -from enum import Enum -import sys -from importlib.metadata import entry_points -from typing import Callable, Dict, Iterable, Union - -from dsms.core.attribute_dict import ImmutableDynamicAttributesDict -from dsms.core.entrypoints import DSMSEntrypoints - - -class DSMSEntrypoints(str, Enum): - """Enum class defining DSMS entrypoints.""" - - SPARQL = "dsms.sparql" - - - -def _get_entrypoints(entrypoint_group: DSMSEntrypoints) -> Iterable: - if not isinstance(entrypoint_group, DSMSEntrypoints): - raise TypeError( - f"""Positional argument for `entrypoint_group` - must be of type {DSMSEntrypoints}, not {type(entrypoint_group)}""" - ) - package_entry_points = entry_points() - if sys.version_info >= (3, 10): - return package_entry_points.select(group=entrypoint_group.value) - return package_entry_points.get(entrypoint_group.value, tuple()) - - -def load_entrypoints( - entrypoint_group: DSMSEntrypoints, names_only: bool = True -) -> Dict[str, Union[str, Callable]]: - """Get dictionary for a specific group of available entry points.""" - return { - entry_point.name: ( - entry_point.name if names_only else entry_point.load() - ) - for entry_point in _get_entrypoints(entrypoint_group) - } - - -def get_entrypoint(name: str, entrypoint_group: DSMSEntrypoints) -> Callable: - """Load a specific class from an entrypoint.""" - plugins = load_entrypoints(entrypoint_group, names_only=False) - if name not in plugins: - raise AttributeError(name) - return plugins[name] - - -def make_attribute_dict( - entrypoint_group: DSMSEntrypoints, - names_only: bool = True, -) -> ImmutableDynamicAttributesDict: - """Make entrypoints for immutable dynamic attribute dict.""" - return ImmutableDynamicAttributesDict( - **load_entrypoints(entrypoint_group, names_only=names_only) - ) - - -class Registry(ImmutableDynamicAttributesDict): - """DSMS Registry for enums of types plugins, mappers, queries, etc. - In order to avoid circular import, the kwargs `load` during initialization - is set to `False`. If set to `True`, the values of the attributes resolve - into callbables instead of strings with the names.""" - - def __init__(self, load=False) -> None: - data = { - obj.name.lower(): make_attribute_dict(obj, names_only=not load) - for obj in list(DSMSEntrypoints) - } - super().__init__(**data) diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index 4994896..0242556 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -6,15 +6,25 @@ from pydantic import Field from dsms.knowledge.properties.base import KProperty, KPropertyItem -from dsms.knowledge.semantics.units import get_conversion_factor, get_property_unit from dsms.knowledge.utils import _get_hdf5_column +from dsms.knowledge.semantics.units import ( # isort:skip + get_conversion_factor, + get_property_unit, +) + if TYPE_CHECKING: from typing import Any, Callable, Dict, List, Optional class Column(KPropertyItem): - """Column of an HDF5 data frame""" + """ + Column of an HDF5 data frame. + + Attributes: + column_id (int): Column ID in the data frame. + name (str): Name of the column in the data series. + """ column_id: int = Field(..., description="Column ID in the data frame") @@ -23,10 +33,21 @@ class Column(KPropertyItem): ) def get(self) -> "List[Any]": - """Download the data for the column in a time series""" + """ + Download the data for the column in a time series. + + Returns: + List[Any]: List of data for the column. + """ return _get_hdf5_column(self.id, self.column_id) def get_unit(self) -> "Dict[str, Any]": + """ + Retrieve the unit of the column. + + Returns: + Dict[str, Any]: Dictionary containing unit information. + """ return get_property_unit(self.id, self.name, is_hdf5_column=True) def convert_to( @@ -35,6 +56,17 @@ def convert_to( decimals: "Optional[int]" = None, use_input_iri: bool = False, ) -> "List[Any]": + """ + Convert the data of the column to a different unit. + + Args: + unit_symbol_or_iri (str): Symbol or IRI of the unit to convert to. + decimals (Optional[int]): Number of decimals to round the result to. Defaults to None. + use_input_iri (bool): If True, use IRI for unit comparison. Defaults to False. + + Returns: + List[Any]: List of converted data. + """ unit = self.get_unit() if use_input_iri: input_str = unit.get("iri") @@ -81,6 +113,8 @@ def to_df(self) -> pd.DataFrame: def get(self, name: str) -> "Optional[Column]": """Get a column with a certain name.""" + response = None for column in self: if column.name == name: - return column + response = column + return response diff --git a/dsms/knowledge/semantics/queries/__init__.py b/dsms/knowledge/semantics/queries/__init__.py index 3eab0fe..7d81dc6 100644 --- a/dsms/knowledge/semantics/queries/__init__.py +++ b/dsms/knowledge/semantics/queries/__init__.py @@ -2,4 +2,4 @@ from .base import BaseSparqlQuery -__all__ = ["BaseSparqlQuery"] \ No newline at end of file +__all__ = ["BaseSparqlQuery"] diff --git a/dsms/knowledge/semantics/queries/base.py b/dsms/knowledge/semantics/queries/base.py index 09b6533..d8c65b6 100644 --- a/dsms/knowledge/semantics/queries/base.py +++ b/dsms/knowledge/semantics/queries/base.py @@ -7,8 +7,10 @@ if TYPE_CHECKING: from typing import Any, Dict, Optional + from dsms import DSMS + class BaseSparqlQuery(ABC): """Abstract class for DSMS Sparql Query""" @@ -32,13 +34,13 @@ def dsms(cls) -> "Dict[str, Any]": return cls._dsms @property - def results(cls) -> "Optional[Dict[str, Any]]": + def results(cls) -> "Optional[Dict[str, Any]]": """Return query results""" return cls._results @property @abstractmethod - def result_mappings(self) -> "Dict[str, Any]": + def result_mappings(cls) -> "Dict[str, Any]": """ Define a mapping between the output keys and the output datatype. E.g. {'foo': int, 'bar': str, 'foobar': MyCustomClass} @@ -59,7 +61,7 @@ def execute(self) -> None: row_converted = {str(key): value for key, value in row.items()} self._results.append( { - name: func(row_converted.get(name)) + name: func(row_converted.get(name)) for name, func in self.result_mappings.items() } ) diff --git a/dsms/knowledge/semantics/units/__init__.py b/dsms/knowledge/semantics/units/__init__.py index 909ac94..95353a0 100644 --- a/dsms/knowledge/semantics/units/__init__.py +++ b/dsms/knowledge/semantics/units/__init__.py @@ -2,7 +2,4 @@ from .utils import get_conversion_factor, get_property_unit -__all__ = [ - "get_conversion_factor", - "get_property_unit" -] +__all__ = ["get_conversion_factor", "get_property_unit"] diff --git a/dsms/knowledge/semantics/units/base.py b/dsms/knowledge/semantics/units/base.py index 07e94b8..0d68cdf 100644 --- a/dsms/knowledge/semantics/units/base.py +++ b/dsms/knowledge/semantics/units/base.py @@ -1,11 +1,11 @@ """DSMS acstract class for units sparql query""" -from dsms.knowledge.semantics.queries import BaseSparqlQuery from typing import TYPE_CHECKING -if TYPE_CHECKING: +from dsms.knowledge.semantics.queries import BaseSparqlQuery - from typing import Union, Dict, Any +if TYPE_CHECKING: + from typing import Any, Dict, Union from uuid import UUID @@ -16,20 +16,20 @@ class BaseUnitSparqlQuery(BaseSparqlQuery): a kitem. """ - def __init__(self, - kitem_id: "Union[str, UUID]", - property_name: str, - is_hdf5_column: bool = False) -> None: + def __init__( + self, + kitem_id: "Union[str, UUID]", + property_name: str, + is_hdf5_column: bool = False, + ) -> None: super().__init__( kitem_id=kitem_id, property_name=property_name, - is_hdf5_column=is_hdf5_column) + is_hdf5_column=is_hdf5_column, + ) # OVERRIDE @property def result_mappings(cls) -> "Dict[str, Any]": """Define mappings for the results of the units sparql queries""" - return { - "symbol": str, - "iri": str - } + return {"symbol": str, "iri": str} diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py index f75f0ba..b61cdc5 100644 --- a/dsms/knowledge/semantics/units/sparql.py +++ b/dsms/knowledge/semantics/units/sparql.py @@ -1,8 +1,10 @@ """Semantic units for a custom property of a KItem in DSMS""" -from dsms.knowledge.semantics.units.base import BaseUnitSparqlQuery from urllib.parse import urljoin +from dsms.knowledge.semantics.units.base import BaseUnitSparqlQuery + + class UnitSparqlQuery(BaseUnitSparqlQuery): """ Define a sparql query for fetching the units of an HDf5 column of a KItem @@ -11,8 +13,8 @@ class UnitSparqlQuery(BaseUnitSparqlQuery): @property def query(cls) -> str: """Construct sparql query for getting unit for hdf5 column""" - kitem_id = cls.kwargs.get("kitem_id") - property_name = cls.kwargs.get("property_name") + kitem_id = cls.kwargs.get("kitem_id") + property_name = cls.kwargs.get("property_name") if not kitem_id: raise ValueError("KItem ID must be defined.") if not property_name: @@ -27,10 +29,10 @@ def query(cls) -> str: prefix perceptual: prefix reductionistic: - select distinct + select distinct (STR(?symbol_literal) as ?symbol) ?iri - where {{ + where {{ <{url}/dataset> datamodel:composition ?prop . @@ -52,10 +54,10 @@ def query(cls) -> str: prefix perceptual: prefix reductionistic: - select distinct + select distinct ?symbol ?iri - where {{ + where {{ <{url}/dataset> datamodel:composition ?prop . diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py index 6c42059..37da41d 100644 --- a/dsms/knowledge/semantics/units/utils.py +++ b/dsms/knowledge/semantics/units/utils.py @@ -1,14 +1,21 @@ """Utilities for unit semantics of a KItem""" -from .conversion import _is_valid_url, _check_qudt_mapping, _units_are_compatible, _get_factor_from_uri -from .sparql import UnitSparqlQuery -from typing import TYPE_CHECKING from functools import lru_cache +from typing import TYPE_CHECKING + +from .conversion import ( + _check_qudt_mapping, + _get_factor_from_uri, + _is_valid_url, + _units_are_compatible, +) +from .sparql import UnitSparqlQuery if TYPE_CHECKING: - from typing import Optional, Dict, Any, Union + from typing import Any, Dict, Optional, Union from uuid import UUID + @lru_cache def get_conversion_factor( original_unit: str, target_unit: str, decimals: "Optional[int]" = None @@ -57,7 +64,12 @@ def get_conversion_factor( factor = round(factor, decimals) return factor -def get_property_unit(kitem_id: "Union[str, UUID]", property_name: str, is_hdf5_column: bool = False) -> "Dict[str, Any]": + +def get_property_unit( + kitem_id: "Union[str, UUID]", + property_name: str, + is_hdf5_column: bool = False, +) -> "Dict[str, Any]": """ Retrieve the unit associated with a given property of a KIitem. @@ -69,7 +81,7 @@ def get_property_unit(kitem_id: "Union[str, UUID]", property_name: str, is_hdf5_ column or a custom property. Defaults to False. Returns: - Dict[str, Any]: A dictionary with the symbol and iri of the unit associated + Dict[str, Any]: A dictionary with the symbol and iri of the unit associated with the specified property. Raises: @@ -79,9 +91,17 @@ def get_property_unit(kitem_id: "Union[str, UUID]", property_name: str, is_hdf5_ try: query = UnitSparqlQuery(kitem_id, property_name, is_hdf5_column) except Exception as error: - raise ValueError(f"Something went wrong catching the unit for property `{property_name}`.") from error + raise ValueError( + f"Something went wrong catching the unit for property `{property_name}`." + ) from error if len(query.results) == 0: - raise ValueError(f"Property `{property_name}` does not own any unit with respect to the semantics applied.") - elif len(query.results) > 1: - raise ValueError(f"Property `{property_name}` owns more than one unit with respect to the semantics applied.") + raise ValueError( + f"""Property `{property_name}` does not own any + unit with respect to the semantics applied.""" + ) + if len(query.results) > 1: + raise ValueError( + f"""Property `{property_name}` owns more than one + unit with respect to the semantics applied.""" + ) return query.results.pop() diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 7eb1ecf..d8e67df 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -59,7 +59,7 @@ "1000.0" ] }, - "execution_count": 8, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -86,7 +86,7 @@ "39370.1" ] }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -104,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -113,7 +113,7 @@ "1000.0" ] }, - "execution_count": 10, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -131,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -140,7 +140,7 @@ "39.37" ] }, - "execution_count": 11, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -160,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -187,7 +187,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -196,7 +196,7 @@ "1.15740740740741e-08" ] }, - "execution_count": 13, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -214,9 +214,19 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original unit: {'symbol': 'N', 'iri': 'http://emmo.info/emmo/middle/siunits#EMMO_a979c531_f9fa_4a6e_93c1_a2960241ca64'}\n", + "\n", + "\n", + "Now the converted data column in kN\n" + ] + }, { "data": { "text/plain": [ @@ -1223,7 +1233,7 @@ " ...]" ] }, - "execution_count": 14, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1232,6 +1242,9 @@ "uuid = \"e49f1e17-f62d-46b2-b23f-72f1642a26ce\"\n", "item = dsms[uuid]\n", "\n", + "print(\"Original unit:\", item.hdf5.get(\"Standardkraft\").get_unit())\n", + "\n", + "print(\"\\n\\nNow the converted data column in kN\")\n", "item.hdf5.get(\"Standardkraft\").convert_to(\"kN\")" ] } diff --git a/tests/test_utils.py b/tests/test_utils.py index b2d531d..a639ae8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -120,9 +120,9 @@ def test_unit_conversion(custom_address): with pytest.warns(UserWarning, match="No authentication details"): DSMS(host_url=custom_address) - assert get_conversion_factor("mm", "m", rounded=3) == 0.001 + assert get_conversion_factor("mm", "m", decimals=3) == 0.001 - assert get_conversion_factor("km", "in", rounded=1) == 39370.1 + assert get_conversion_factor("km", "in", decimals=1) == 39370.1 assert get_conversion_factor("GPa", "MPa") == 1000 @@ -130,7 +130,7 @@ def test_unit_conversion(custom_address): get_conversion_factor( "http://qudt.org/vocab/unit/M", "http://qudt.org/vocab/unit/IN", - rounded=1, + decimals=1, ) == 39.4 ) From 54e0519c9867cc41cd781c3bb97d5b9296a186ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 9 Apr 2024 13:42:27 +0200 Subject: [PATCH 008/114] make units sparql object configurable --- dsms/core/configuration.py | 23 ++++++++++++++++++++++- dsms/core/utils.py | 14 ++++++++++++-- dsms/knowledge/semantics/units/utils.py | 11 +++++++++-- examples/unit_conversion.ipynb | 20 ++++++++++---------- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 5adf1e1..c39d42f 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -2,13 +2,21 @@ import urllib import warnings -from typing import Optional +from typing import TYPE_CHECKING, Optional import requests from pydantic import AnyUrl, Field, SecretStr, field_validator from pydantic_core.core_schema import ValidationInfo from pydantic_settings import BaseSettings, SettingsConfigDict +from .utils import get_callable + +if TYPE_CHECKING: + from typing import Callable + +MODULE_REGEX = r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*:[a-zA-Z_][a-zA-Z0-9_]*$" +DEFAULT_UNIT_SPARQL = "dsms.knowledge.semantics.units.sparql:UnitSparqlQuery" + class Configuration(BaseSettings): """General config for DSMS-SDK""" @@ -63,6 +71,19 @@ class Configuration(BaseSettings): description="URI to QUDT quantity kind ontology for unit conversion", ) + units_sparql_object: str = Field( + DEFAULT_UNIT_SPARQL, + pattern=MODULE_REGEX, + description="""Class and Module specification in Python for a subclass of + `dsms.knowledge.semantics.units.base:BaseUnitSparqlQuery` in order to retrieve + the units of a HDF5 column/ custom property of a KItem.""", + ) + + @field_validator("units_sparql_object") + def get_unit_sparql_object(cls, val: str) -> "Callable": + """Source the class from the given module""" + return get_callable(val) + @field_validator("token") def validate_auth(cls, val, info: ValidationInfo): """Validate the provided authentication/authorization secrets.""" diff --git a/dsms/core/utils.py b/dsms/core/utils.py index 33bdd5b..d2382cb 100644 --- a/dsms/core/utils.py +++ b/dsms/core/utils.py @@ -1,12 +1,16 @@ """Core utils of the DSMS core""" import re -from typing import Any +from importlib import import_module +from typing import TYPE_CHECKING from urllib.parse import urljoin from uuid import UUID import requests from requests import Response +if TYPE_CHECKING: + from typing import Any, Callable + def _kitem_id2uri(kitem_id: UUID) -> str: "Convert a kitem id in the DSMS to the full resolvable URI" @@ -27,7 +31,7 @@ def _ping_dsms(): return _perform_request("api/knowledge/docs", "get") -def _perform_request(route: str, method: str, **kwargs: Any) -> Response: +def _perform_request(route: str, method: str, **kwargs: "Any") -> Response: """Perform a general request for a certain route and with a certain method. Kwargs are general arguments which can be passed to the `requests.request`-function. """ @@ -63,3 +67,9 @@ def _name_to_camel(input_string): camel_case_words = [word.title() for word in words] camel_case_string = "".join(camel_case_words) return camel_case_string + + +def get_callable(module: str) -> "Callable": + """Get callable from import-specification""" + module, classname = module.strip().split(":") + return getattr(import_module(module), classname) diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py index 37da41d..0ab6638 100644 --- a/dsms/knowledge/semantics/units/utils.py +++ b/dsms/knowledge/semantics/units/utils.py @@ -3,13 +3,13 @@ from functools import lru_cache from typing import TYPE_CHECKING +from .base import BaseUnitSparqlQuery from .conversion import ( _check_qudt_mapping, _get_factor_from_uri, _is_valid_url, _units_are_compatible, ) -from .sparql import UnitSparqlQuery if TYPE_CHECKING: from typing import Any, Dict, Optional, Union @@ -88,8 +88,15 @@ def get_property_unit( ValueError: If unable to retrieve the unit for the property due to any errors or if the property does not have a unit or has more than one unit associated with it. """ + from dsms import Context + + units_sparql_object = Context.dsms.config.units_sparql_object + if not issubclass(units_sparql_object, BaseUnitSparqlQuery): + raise TypeError( + f"´{units_sparql_object}´ must be a subclass of `{BaseUnitSparqlQuery}`" + ) try: - query = UnitSparqlQuery(kitem_id, property_name, is_hdf5_column) + query = units_sparql_object(kitem_id, property_name, is_hdf5_column) except Exception as error: raise ValueError( f"Something went wrong catching the unit for property `{property_name}`." diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index d8e67df..a03b66b 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -104,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -131,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -160,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -187,7 +187,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -214,7 +214,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -1233,7 +1233,7 @@ " ...]" ] }, - "execution_count": 18, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } From e7d5e796996e433064d30c788ee5faeb324f6110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 9 Apr 2024 17:45:53 +0200 Subject: [PATCH 009/114] add custom datatype for unit conversion of individual properties --- dsms/core/dsms.py | 49 ++++++++++++- dsms/knowledge/ktype.py | 20 +++--- .../properties/custom_datatype/__init__.py | 72 +++++++++++++++++++ .../knowledge/properties/custom_properties.py | 21 +++++- dsms/knowledge/utils.py | 62 +++++++++------- examples/unit_conversion.ipynb | 40 +++++------ setup.cfg | 1 + 7 files changed, 199 insertions(+), 66 deletions(-) create mode 100644 dsms/knowledge/properties/custom_datatype/__init__.py diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index 68511e2..156a8cf 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -1,7 +1,10 @@ """DSMS connection module""" +import os from typing import TYPE_CHECKING, Any, Dict, List +from dotenv import load_dotenv + from dsms.apps.utils import _get_available_apps from dsms.core.configuration import Configuration from dsms.core.context import Context @@ -27,16 +30,56 @@ class DSMS: - """General class for connecting and interfacting with DSMS.""" + """ + General class for connecting and interfacing with DSMS. + + This class provides methods to connect to and interact with a DSMS (Data + Space Management System) instance. It abstracts away the complexities of + establishing connections and executing queries. + + Args: + config (Configuration, optional): An optional Configuration object + containing connection details. If not provided, default + configurations will be used. + env (str, optional): An optional string representing the path to the env-file. + This can be used to select environment-specific configurations. Defaults to None. + **kwargs: Configurations can also be set as additional keyword arguments instead of + passing the path to an env-file or the Configuration-object itself. + + """ _context = Context - def __init__(self, config: Configuration = None, **kwargs) -> None: - """Initialize the DSMS object.""" + def __init__( + self, + config: "Optional[Configuration]" = None, + env: "Optional[str]" = None, + **kwargs, + ) -> None: + """Initialize the DSMS object. + Args: + config (Configuration, optional): An optional Configuration object + containing connection details. If not provided, default + configurations will be used. + env (str, optional): An optional string representing the path to the env-file. + This can be used to select environment-specific configurations. Content + of the env-file will be safely loaded using `python-dotenv`. + Hence the env-variables will be pruned once the kernel is closed. + Defaults to None. + **kwargs: Configurations can also be set as additional keyword arguments instead of + passing the path to an env-file or the Configuration-object itself. + """ self._config = None self._context.dsms = self + if env: + if not os.path.exists(env): + raise OSError(f"File `{env}` does not exist") + loaded = load_dotenv(env, verbose=True) + if not loaded: + raise RuntimeError(f"Not able to parse .env file: {env}") + if config is not None and not kwargs: self.config = config elif config is None: diff --git a/dsms/knowledge/ktype.py b/dsms/knowledge/ktype.py index 576811e..dccebd2 100644 --- a/dsms/knowledge/ktype.py +++ b/dsms/knowledge/ktype.py @@ -15,22 +15,22 @@ class KType(BaseModel): name: Optional[str] = Field( None, description="Human readable name of the KType." ) - form_data: Optional[Any] = Field( - None, description="Form data of the KItem." - ) - data_schema: Optional[Any] = Field( + webform: Optional[Any] = Field(None, description="Form data of the KItem.") + json_schema: Optional[Any] = Field( None, description="OpenAPI schema of the KItem." ) - @field_validator("data_schema") + @field_validator("webform") @classmethod - def validate_data_schema(cls, value) -> Dict[str, Any]: + def create_model(cls, value) -> Dict[str, Any]: """Validate the data schema of the ktype""" + if not isinstance(value, (type(None), dict)): + raise TypeError(f"Invalid type for `webform`: {type(value)}") if isinstance(value, dict): - value = _parse_model(value) - elif not isinstance(value, BaseModel): - raise TypeError(f"Invalid type for `data_schema`: {type(value)}") - return value + model = _parse_model(value) + else: + model = None + return model @model_serializer def serialize(self): diff --git a/dsms/knowledge/properties/custom_datatype/__init__.py b/dsms/knowledge/properties/custom_datatype/__init__.py new file mode 100644 index 0000000..02f1b4f --- /dev/null +++ b/dsms/knowledge/properties/custom_datatype/__init__.py @@ -0,0 +1,72 @@ +"""Module for custom data types""" + +from typing import TYPE_CHECKING + +from dsms.knowledge.semantics.units.utils import ( + get_conversion_factor, + get_property_unit, +) + +if TYPE_CHECKING: + from typing import Any, Dict, Optional + + from dsms import KItem + + +class NumericalDataType(float): + """Custom Base data type for custom properties""" + + def __init__(self, *args) -> None: + self._kitem: "Optional[KItem]" = None + self._property_name: "Optional[KItem]" = None + super().__init__(*args) + + @property + def kitem(cls) -> "KItem": + """Context of the current kitem for this property""" + return cls._kitem + + @kitem.setter + def kitem(cls, value: "KItem") -> None: + """Setter for current KItem context""" + cls._kitem = value + + @property + def name(cls) -> str: + """Context of the name for this property""" + return cls._name + + @name.setter + def name(cls, value: str) -> None: + """Setter for the name of the property""" + cls._name = value + + def get_unit(self) -> "Dict[str, Any]": + """Get unit for the property""" + return get_property_unit(self.kitem.id, self.name) + + def convert_to( + self, + unit_symbol_or_iri: str, + decimals: "Optional[int]" = None, + use_input_iri: bool = False, + ) -> float: + """ + Convert the data of property to a different unit. + + Args: + unit_symbol_or_iri (str): Symbol or IRI of the unit to convert to. + decimals (Optional[int]): Number of decimals to round the result to. Defaults to None. + use_input_iri (bool): If True, use IRI for unit comparison. Defaults to False. + + Returns: + float: converted value of the property + """ + unit = self.get_unit() + if use_input_iri: + input_str = unit.get("iri") + else: + input_str = unit.get("symbol") + return self * get_conversion_factor( + input_str, unit_symbol_or_iri, decimals=decimals + ) diff --git a/dsms/knowledge/properties/custom_properties.py b/dsms/knowledge/properties/custom_properties.py index db4a7c9..7cf77b7 100644 --- a/dsms/knowledge/properties/custom_properties.py +++ b/dsms/knowledge/properties/custom_properties.py @@ -25,6 +25,13 @@ class CustomProperties(BaseModel): extra="forbid", exclude={"kitem", "id"}, validate_assignment=True ) + def __setattr__(self, name, value) -> None: + """Set id and name of properties when attribute is set""" + super().__setattr__(name, value) + if name == "content": + self._mark_as_updated() + self._set_id_and_name_of_content() + def __str__(self) -> str: """Pretty print the custom properties""" fields = ", ".join( @@ -66,6 +73,14 @@ def add(self, content: "Union[dict, Any]") -> None: ) self._mark_as_updated() + def _set_id_and_name_of_content(self) -> None: + for subproperty in self.content.__fields__(): + for key, value in subproperty.__dict__.items(): + if not value.name: + value.name = key + if not value.kitem_id: + value.kitem_id = self.id + def _mark_as_updated(self) -> None: if self.kitem and self.id not in self.context.buffers.updated: self.context.buffers.updated.update({self.id: self.kitem}) @@ -92,8 +107,8 @@ def validate_kitem(cls, data: Any) -> "KItem": content = data.get("content") if kitem: data["id"] = kitem.id - if kitem.ktype.data_schema: - data["content"] = kitem.ktype.data_schema(**content) + if kitem.ktype.webform: + data["content"] = kitem.ktype.webform(**content) return data @property @@ -108,7 +123,7 @@ def data_schema(cls): """Data schema related to the ktype of the associated kitem.""" if not cls.kitem: raise ValueError("KItem not defined yet.") - return cls.kitem.ktype.data_schema # pylint: disable=E1101 + return cls.kitem.ktype.webform # pylint: disable=E1101 @property def context(cls) -> "Context": diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 31db7b5..696b26e 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -2,16 +2,18 @@ import io import json import re +import warnings from enum import Enum from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from uuid import UUID import pandas as pd -from pydantic import BaseModel, create_model +from pydantic import BaseModel, ConfigDict, create_model from requests import Response from dsms.core.utils import _name_to_camel, _perform_request +from dsms.knowledge.properties.custom_datatype import NumericalDataType if TYPE_CHECKING: from dsms.core.context import Buffers @@ -20,30 +22,38 @@ def _parse_model(value: Dict[str, Any]) -> BaseModel: """Convert the dict with the model schema into a pydantic model.""" - title = value.get("title") - properties = value.get("properties") - if not title: - raise KeyError("`data_schema` does not have any `title`.") - if isinstance(properties, type(None)): - raise KeyError("`data_schema` does not have any `properties`.") + fields = {} - for key, props in properties.items(): - dtype = props.get("type") - default = props.get("default") - if dtype == "string": - dtype = str - elif dtype == "integer": - dtype = int - elif dtype == "float": - dtype = float - elif dtype == "bool": - dtype = bool - elif dtype == "object": - dtype = dict - else: - raise TypeError(f"Invalid `type={dtype}`") - fields[key] = (dtype, ... if not default else default) - return create_model(title, **fields) + title = _name_to_camel(value.get("title")) + for item in value.get("objects"): + for form_input in item.get("inputs"): + label = form_input.get("label") + dtype = form_input.get("widget") + default = form_input.get("defaultValue") + slug = _slugify(label) + if dtype in ("text", "file"): + dtype = str + elif dtype in ("number", "slider"): + dtype = NumericalDataType + elif dtype == "checkbox": + dtype = bool + elif dtype in ("select", "radio"): + dtype = Enum( + _name_to_camel(label) + "Choices", + { + _name_to_camel(choice["value"]): choice["value"] + for choice in form_input.get("choices") + }, + ) + elif dtype == "knowledge-select": + warnings.warn("knowledge-select not supported for KTypes yet.") + fields[slug] = (dtype, ... if not default else default) + if fields: + config = ConfigDict(extra="allow") + model = create_model(title, __config__=config, **fields) + else: + model = None + return model def _get_remote_ktypes() -> Enum: @@ -335,10 +345,10 @@ def _search( return [KItem(**item) for item in dumped] -def _slugify(input_string): +def _slugify(input_string: str, replacement: str = ""): """Turn any arbitrary string into a slug.""" slug = re.sub( - r"[^\w\s]", "", input_string + r"[^\w\s]", replacement, input_string ) # Remove all non-word characters (everything except numbers and letters) slug = re.sub(r"\s+", "", slug) # Replace all runs of whitespace slug = slug.lower() # Convert the string to lowercase. diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index a03b66b..5015732 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -13,12 +13,8 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS\n", - "from dsms.knowledge.semantics.units import get_conversion_factor\n" + "from dsms.knowledge.semantics.units import get_conversion_factor" ] }, { @@ -30,15 +26,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" ] }, { @@ -50,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -59,7 +51,7 @@ "1000.0" ] }, - "execution_count": 12, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -77,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -86,7 +78,7 @@ "39370.1" ] }, - "execution_count": 13, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -104,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -113,7 +105,7 @@ "1000.0" ] }, - "execution_count": 14, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -131,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -140,7 +132,7 @@ "39.37" ] }, - "execution_count": 15, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -160,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -187,7 +179,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -196,7 +188,7 @@ "1.15740740740741e-08" ] }, - "execution_count": 17, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -214,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -1233,7 +1225,7 @@ " ...]" ] }, - "execution_count": 3, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } diff --git a/setup.cfg b/setup.cfg index e9fb3cf..dea3812 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ install_requires = pandas>=2,<3 pydantic>=2 pydantic-settings + python-dotenv rdflib>=6,<7 requests python_requires = >=3.8 From 32b89936aa2cfe9d3aa957039c8322e138639ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 10 Apr 2024 11:37:19 +0200 Subject: [PATCH 010/114] finalize conversion of custom properties --- dsms/knowledge/ktype.py | 10 +- .../properties/custom_datatype/__init__.py | 73 +- .../properties/custom_datatype/numerical.py | 70 ++ .../knowledge/properties/custom_properties.py | 46 +- dsms/knowledge/semantics/units/sparql.py | 6 +- dsms/knowledge/utils.py | 79 +- examples/unit_conversion.ipynb | 1029 +---------------- 7 files changed, 165 insertions(+), 1148 deletions(-) create mode 100644 dsms/knowledge/properties/custom_datatype/numerical.py diff --git a/dsms/knowledge/ktype.py b/dsms/knowledge/ktype.py index dccebd2..62f8cbd 100644 --- a/dsms/knowledge/ktype.py +++ b/dsms/knowledge/ktype.py @@ -22,15 +22,9 @@ class KType(BaseModel): @field_validator("webform") @classmethod - def create_model(cls, value) -> Dict[str, Any]: + def create_model(cls, value: Optional[Dict[str, Any]]) -> Dict[str, Any]: """Validate the data schema of the ktype""" - if not isinstance(value, (type(None), dict)): - raise TypeError(f"Invalid type for `webform`: {type(value)}") - if isinstance(value, dict): - model = _parse_model(value) - else: - model = None - return model + return _parse_model(value) @model_serializer def serialize(self): diff --git a/dsms/knowledge/properties/custom_datatype/__init__.py b/dsms/knowledge/properties/custom_datatype/__init__.py index 02f1b4f..f53b7d1 100644 --- a/dsms/knowledge/properties/custom_datatype/__init__.py +++ b/dsms/knowledge/properties/custom_datatype/__init__.py @@ -1,72 +1,5 @@ -"""Module for custom data types""" +"""Custom data type module""" -from typing import TYPE_CHECKING +from .numerical import NumericalDataType -from dsms.knowledge.semantics.units.utils import ( - get_conversion_factor, - get_property_unit, -) - -if TYPE_CHECKING: - from typing import Any, Dict, Optional - - from dsms import KItem - - -class NumericalDataType(float): - """Custom Base data type for custom properties""" - - def __init__(self, *args) -> None: - self._kitem: "Optional[KItem]" = None - self._property_name: "Optional[KItem]" = None - super().__init__(*args) - - @property - def kitem(cls) -> "KItem": - """Context of the current kitem for this property""" - return cls._kitem - - @kitem.setter - def kitem(cls, value: "KItem") -> None: - """Setter for current KItem context""" - cls._kitem = value - - @property - def name(cls) -> str: - """Context of the name for this property""" - return cls._name - - @name.setter - def name(cls, value: str) -> None: - """Setter for the name of the property""" - cls._name = value - - def get_unit(self) -> "Dict[str, Any]": - """Get unit for the property""" - return get_property_unit(self.kitem.id, self.name) - - def convert_to( - self, - unit_symbol_or_iri: str, - decimals: "Optional[int]" = None, - use_input_iri: bool = False, - ) -> float: - """ - Convert the data of property to a different unit. - - Args: - unit_symbol_or_iri (str): Symbol or IRI of the unit to convert to. - decimals (Optional[int]): Number of decimals to round the result to. Defaults to None. - use_input_iri (bool): If True, use IRI for unit comparison. Defaults to False. - - Returns: - float: converted value of the property - """ - unit = self.get_unit() - if use_input_iri: - input_str = unit.get("iri") - else: - input_str = unit.get("symbol") - return self * get_conversion_factor( - input_str, unit_symbol_or_iri, decimals=decimals - ) +__all__ = ["NumericalDataType"] diff --git a/dsms/knowledge/properties/custom_datatype/numerical.py b/dsms/knowledge/properties/custom_datatype/numerical.py new file mode 100644 index 0000000..16d13cb --- /dev/null +++ b/dsms/knowledge/properties/custom_datatype/numerical.py @@ -0,0 +1,70 @@ +"""Module for custom numerical data type""" + +from typing import TYPE_CHECKING + +from dsms.knowledge.semantics.units.utils import ( + get_conversion_factor, + get_property_unit, +) + +if TYPE_CHECKING: + from typing import Any, Dict, Optional, Union + from uuid import UUID + + +class NumericalDataType(float): + """Custom Base data type for custom properties""" + + def __init__(self, value) -> None: # pylint: disable=unused-argument + self._kitem_id: "Optional[Union[str, UUID]]" = None + self._name: "Optional[str]" = None + + @property + def kitem_id(cls) -> "Optional[Union[str, UUID]]": + """Context of the current kitem for this property""" + return cls._kitem_id + + @kitem_id.setter + def kitem_id(cls, value: "Optional[Union[str, UUID]]") -> None: + """Setter for current KItem context""" + cls._kitem_id = value + + @property + def name(cls) -> str: + """Context of the name for this property""" + return cls._name + + @name.setter + def name(cls, value: str) -> None: + """Setter for the name of the property""" + cls._name = value + + def get_unit(self) -> "Dict[str, Any]": + """Get unit for the property""" + return get_property_unit(self.kitem_id, self.name) + + def convert_to( + self, + unit_symbol_or_iri: str, + decimals: "Optional[int]" = None, + use_input_iri: bool = False, + ) -> float: + """ + Convert the data of property to a different unit. + + Args: + unit_symbol_or_iri (str): Symbol or IRI of the unit to convert to. + decimals (Optional[int]): Number of decimals to round the result to. Defaults to None. + use_input_iri (bool): If True, use IRI for unit comparison. Defaults to False. + + Returns: + float: converted value of the property + """ + unit = self.get_unit() + if use_input_iri: + input_str = unit.get("iri") + else: + input_str = unit.get("symbol") + return self * get_conversion_factor( + input_str, unit_symbol_or_iri, decimals=decimals + ) diff --git a/dsms/knowledge/properties/custom_properties.py b/dsms/knowledge/properties/custom_properties.py index 7cf77b7..ddc63b2 100644 --- a/dsms/knowledge/properties/custom_properties.py +++ b/dsms/knowledge/properties/custom_properties.py @@ -6,10 +6,12 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator +from .custom_datatype.numerical import NumericalDataType + if TYPE_CHECKING: from typing import Set, Union - from dsms import Context, KItem + from dsms import Context class CustomProperties(BaseModel): @@ -25,13 +27,6 @@ class CustomProperties(BaseModel): extra="forbid", exclude={"kitem", "id"}, validate_assignment=True ) - def __setattr__(self, name, value) -> None: - """Set id and name of properties when attribute is set""" - super().__setattr__(name, value) - if name == "content": - self._mark_as_updated() - self._set_id_and_name_of_content() - def __str__(self) -> str: """Pretty print the custom properties""" fields = ", ".join( @@ -73,14 +68,6 @@ def add(self, content: "Union[dict, Any]") -> None: ) self._mark_as_updated() - def _set_id_and_name_of_content(self) -> None: - for subproperty in self.content.__fields__(): - for key, value in subproperty.__dict__.items(): - if not value.name: - value.name = key - if not value.kitem_id: - value.kitem_id = self.id - def _mark_as_updated(self) -> None: if self.kitem and self.id not in self.context.buffers.updated: self.context.buffers.updated.update({self.id: self.kitem}) @@ -99,17 +86,26 @@ def get(self) -> Any: """Get data for custom properties""" return self.content - @model_validator(mode="before") + @model_validator(mode="after") @classmethod - def validate_kitem(cls, data: Any) -> "KItem": + def validate_content(cls, self) -> "CustomProperties": """Validate the custom properties with respect to the KType of the KItem""" - kitem = data.get("kitem") - content = data.get("content") - if kitem: - data["id"] = kitem.id - if kitem.ktype.webform: - data["content"] = kitem.ktype.webform(**content) - return data + if self.kitem and isinstance(self.content, dict): + # validate content with webform model + if self.kitem.ktype.webform: + self.content = self.kitem.ktype.webform(**self.content) + # set name and property of the inidivdual properties + if isinstance(self.content, dict): + iterable = self.content + else: + iterable = self.content.dict() + for name, subproperty in iterable.items(): + if isinstance(subproperty, NumericalDataType): + if not subproperty.name: + subproperty.name = name + if not subproperty.kitem_id: + subproperty.kitem_id = self.kitem.id + return self @property def id(cls) -> Optional[UUID]: diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py index b61cdc5..6c70b27 100644 --- a/dsms/knowledge/semantics/units/sparql.py +++ b/dsms/knowledge/semantics/units/sparql.py @@ -68,10 +68,10 @@ def query(cls) -> str: ?numeric rdf:type math:EMMO_4ce76d7f_03f8_45b6_9003_90052a79bfaa ; metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?unit , ?literal . + ?unit rdf:type ?iri . ?literal a metro:UnitLiteral ; - perceptual:EMMO_23b579e1_8088_45b5_9975_064014026c42 ?symbol . - - ?unit reductionistic:EMMO_b2282816_b7a3_44c6_b2cb_3feff1ceb7fe ?iri . + metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?symbol_literal . + FILTER(?iri != metro:UnitLiteral) }} """ return query diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 696b26e..579aa83 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -9,7 +9,7 @@ from uuid import UUID import pandas as pd -from pydantic import BaseModel, ConfigDict, create_model +from pydantic import BaseModel, ConfigDict, create_model, model_validator from requests import Response from dsms.core.utils import _name_to_camel, _perform_request @@ -20,42 +20,65 @@ from dsms.knowledge import KItem, KType -def _parse_model(value: Dict[str, Any]) -> BaseModel: +def _is_number(value): + try: + float(value) + return True + except Exception: + return False + + +def _parse_model(value: Optional[Dict[str, Any]]) -> BaseModel: """Convert the dict with the model schema into a pydantic model.""" fields = {} - title = _name_to_camel(value.get("title")) - for item in value.get("objects"): - for form_input in item.get("inputs"): - label = form_input.get("label") - dtype = form_input.get("widget") - default = form_input.get("defaultValue") - slug = _slugify(label) - if dtype in ("text", "file"): - dtype = str - elif dtype in ("number", "slider"): - dtype = NumericalDataType - elif dtype == "checkbox": - dtype = bool - elif dtype in ("select", "radio"): - dtype = Enum( - _name_to_camel(label) + "Choices", - { - _name_to_camel(choice["value"]): choice["value"] - for choice in form_input.get("choices") - }, - ) - elif dtype == "knowledge-select": - warnings.warn("knowledge-select not supported for KTypes yet.") - fields[slug] = (dtype, ... if not default else default) + if isinstance(value, dict): + title = _name_to_camel(value.get("title")) + for item in value.get("objects"): + for form_input in item.get("inputs"): + label = form_input.get("label") + dtype = form_input.get("widget") + default = form_input.get("defaultValue") + slug = _slugify(label) + if dtype in ("text", "file"): + dtype = str + elif dtype in ("number", "slider"): + dtype = NumericalDataType + elif dtype == "checkbox": + dtype = bool + elif dtype in ("select", "radio"): + dtype = Enum( + _name_to_camel(label) + "Choices", + { + _name_to_camel(choice["value"]): choice["value"] + for choice in form_input.get("choices") + }, + ) + elif dtype == "knowledge-select": + warnings.warn( + "knowledge-select not supported for KTypes yet." + ) + fields[slug] = (dtype, default or None) if fields: - config = ConfigDict(extra="allow") - model = create_model(title, __config__=config, **fields) + config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + validators = { + "validate_model": model_validator(mode="before")(_validate_model) + } + model = create_model( + title, __config__=config, __validators__=validators, **fields + ) else: model = None return model +def _validate_model(cls, values: dict): # pylint: disable=unused-argument + for key, value in values.items(): + if _is_number(value): + values[key] = NumericalDataType(value) + return values + + def _get_remote_ktypes() -> Enum: """Get the KTypes from the remote backend""" from dsms import ( # isort:skip diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 5015732..a502b51 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -26,11 +26,11 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(host_url=\"https://stahldigital.materials-data.space\", env=\"../.env\")" ] }, { @@ -206,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -220,1024 +220,25 @@ ] }, { - "data": { - "text/plain": [ - "[0.0819691,\n", - " 0.0820348,\n", - " 0.0820854,\n", - " 0.0820619,\n", - " 0.0820386,\n", - " 0.0820924,\n", - " 0.0821014,\n", - " 0.08210890000000001,\n", - " 0.0821103,\n", - " 0.0822442,\n", - " 0.0827859,\n", - " 0.08363190000000001,\n", - " 0.0846336,\n", - " 0.0856447,\n", - " 0.0866605,\n", - " 0.0876807,\n", - " 0.0886995,\n", - " 0.0897311,\n", - " 0.0907341,\n", - " 0.0917502,\n", - " 0.09278,\n", - " 0.09379269999999999,\n", - " 0.0948254,\n", - " 0.09582760000000001,\n", - " 0.096832,\n", - " 0.09786990000000001,\n", - " 0.0988997,\n", - " 0.0999365,\n", - " 0.100964,\n", - " 0.101998,\n", - " 0.10302800000000001,\n", - " 0.104029,\n", - " 0.105075,\n", - " 0.106114,\n", - " 0.10714,\n", - " 0.10819100000000001,\n", - " 0.109229,\n", - " 0.110281,\n", - " 0.111333,\n", - " 0.112383,\n", - " 0.113418,\n", - " 0.11445699999999999,\n", - " 0.11551399999999999,\n", - " 0.116562,\n", - " 0.11763,\n", - " 0.1187,\n", - " 0.119718,\n", - " 0.12075100000000001,\n", - " 0.12181600000000001,\n", - " 0.12288500000000001,\n", - " 0.123891,\n", - " 0.124923,\n", - " 0.125973,\n", - " 0.127044,\n", - " 0.12804400000000002,\n", - " 0.129065,\n", - " 0.130079,\n", - " 0.131119,\n", - " 0.13218000000000002,\n", - " 0.13324100000000003,\n", - " 0.134311,\n", - " 0.135313,\n", - " 0.13631800000000002,\n", - " 0.137338,\n", - " 0.13836099999999998,\n", - " 0.139367,\n", - " 0.140405,\n", - " 0.141445,\n", - " 0.14248,\n", - " 0.143527,\n", - " 0.144568,\n", - " 0.14561500000000002,\n", - " 0.14665799999999998,\n", - " 0.14772100000000002,\n", - " 0.148785,\n", - " 0.149847,\n", - " 0.150923,\n", - " 0.151993,\n", - " 0.153091,\n", - " 0.15418,\n", - " 0.15527000000000002,\n", - " 0.156367,\n", - " 0.15746700000000002,\n", - " 0.158492,\n", - " 0.159524,\n", - " 0.160537,\n", - " 0.161571,\n", - " 0.16262000000000001,\n", - " 0.16367400000000001,\n", - " 0.164722,\n", - " 0.165773,\n", - " 0.16685400000000003,\n", - " 0.167942,\n", - " 0.16902799999999998,\n", - " 0.170118,\n", - " 0.171206,\n", - " 0.172313,\n", - " 0.17341800000000002,\n", - " 0.174435,\n", - " 0.175474,\n", - " 0.17650100000000002,\n", - " 0.17753200000000002,\n", - " 0.178574,\n", - " 0.179602,\n", - " 0.180634,\n", - " 0.18166800000000002,\n", - " 0.18272300000000002,\n", - " 0.18377500000000002,\n", - " 0.18484,\n", - " 0.185898,\n", - " 0.18697,\n", - " 0.188062,\n", - " 0.189148,\n", - " 0.19023500000000002,\n", - " 0.19131700000000001,\n", - " 0.19243000000000002,\n", - " 0.193545,\n", - " 0.194664,\n", - " 0.195666,\n", - " 0.196791,\n", - " 0.197792,\n", - " 0.19891399999999998,\n", - " 0.199915,\n", - " 0.200917,\n", - " 0.202035,\n", - " 0.203046,\n", - " 0.204075,\n", - " 0.2051,\n", - " 0.206147,\n", - " 0.207202,\n", - " 0.208254,\n", - " 0.20929499999999998,\n", - " 0.21035800000000002,\n", - " 0.211423,\n", - " 0.212478,\n", - " 0.213535,\n", - " 0.214605,\n", - " 0.215678,\n", - " 0.216763,\n", - " 0.217838,\n", - " 0.21890600000000002,\n", - " 0.219975,\n", - " 0.221052,\n", - " 0.22215000000000001,\n", - " 0.223251,\n", - " 0.224351,\n", - " 0.225456,\n", - " 0.226578,\n", - " 0.227708,\n", - " 0.22871100000000003,\n", - " 0.229712,\n", - " 0.230716,\n", - " 0.231718,\n", - " 0.23286,\n", - " 0.233988,\n", - " 0.23512200000000003,\n", - " 0.236136,\n", - " 0.23715,\n", - " 0.238162,\n", - " 0.23919300000000002,\n", - " 0.240231,\n", - " 0.241262,\n", - " 0.242287,\n", - " 0.243316,\n", - " 0.244346,\n", - " 0.245377,\n", - " 0.246418,\n", - " 0.247471,\n", - " 0.248542,\n", - " 0.249613,\n", - " 0.250686,\n", - " 0.251741,\n", - " 0.252801,\n", - " 0.253871,\n", - " 0.254951,\n", - " 0.25602800000000003,\n", - " 0.25711700000000004,\n", - " 0.25819600000000004,\n", - " 0.259282,\n", - " 0.26036200000000004,\n", - " 0.261452,\n", - " 0.262552,\n", - " 0.263659,\n", - " 0.264779,\n", - " 0.265905,\n", - " 0.267023,\n", - " 0.26813200000000004,\n", - " 0.26924400000000004,\n", - " 0.270358,\n", - " 0.27148300000000003,\n", - " 0.27261900000000006,\n", - " 0.27376100000000003,\n", - " 0.27490499999999995,\n", - " 0.27606200000000003,\n", - " 0.277214,\n", - " 0.27835899999999997,\n", - " 0.27950400000000003,\n", - " 0.280652,\n", - " 0.2818,\n", - " 0.282957,\n", - " 0.283963,\n", - " 0.284974,\n", - " 0.28598,\n", - " 0.287146,\n", - " 0.288148,\n", - " 0.289151,\n", - " 0.290171,\n", - " 0.29119,\n", - " 0.29221199999999997,\n", - " 0.293236,\n", - " 0.294262,\n", - " 0.295286,\n", - " 0.296313,\n", - " 0.297333,\n", - " 0.298352,\n", - " 0.29936900000000005,\n", - " 0.300394,\n", - " 0.30143200000000003,\n", - " 0.30248,\n", - " 0.303524,\n", - " 0.304564,\n", - " 0.305611,\n", - " 0.30666000000000004,\n", - " 0.30770600000000004,\n", - " 0.308751,\n", - " 0.309799,\n", - " 0.310849,\n", - " 0.311906,\n", - " 0.31297,\n", - " 0.314039,\n", - " 0.315106,\n", - " 0.316166,\n", - " 0.317229,\n", - " 0.318288,\n", - " 0.319349,\n", - " 0.320409,\n", - " 0.321476,\n", - " 0.322549,\n", - " 0.32362,\n", - " 0.32469,\n", - " 0.325758,\n", - " 0.326818,\n", - " 0.32787900000000003,\n", - " 0.32895100000000005,\n", - " 0.330022,\n", - " 0.331087,\n", - " 0.33216,\n", - " 0.333237,\n", - " 0.334306,\n", - " 0.335384,\n", - " 0.336463,\n", - " 0.337534,\n", - " 0.338606,\n", - " 0.339687,\n", - " 0.340767,\n", - " 0.34185000000000004,\n", - " 0.342943,\n", - " 0.344037,\n", - " 0.345123,\n", - " 0.346212,\n", - " 0.34730500000000003,\n", - " 0.348402,\n", - " 0.349494,\n", - " 0.350592,\n", - " 0.35170100000000004,\n", - " 0.352801,\n", - " 0.35390499999999997,\n", - " 0.355011,\n", - " 0.356119,\n", - " 0.357233,\n", - " 0.35834699999999997,\n", - " 0.359462,\n", - " 0.360576,\n", - " 0.361702,\n", - " 0.362843,\n", - " 0.363991,\n", - " 0.365137,\n", - " 0.366287,\n", - " 0.367437,\n", - " 0.368587,\n", - " 0.369739,\n", - " 0.370897,\n", - " 0.37205200000000005,\n", - " 0.373214,\n", - " 0.374373,\n", - " 0.37553800000000004,\n", - " 0.376709,\n", - " 0.377877,\n", - " 0.379041,\n", - " 0.380214,\n", - " 0.38138299999999997,\n", - " 0.382556,\n", - " 0.38373399999999996,\n", - " 0.384919,\n", - " 0.386101,\n", - " 0.387281,\n", - " 0.388459,\n", - " 0.38963400000000004,\n", - " 0.390823,\n", - " 0.391824,\n", - " 0.392824,\n", - " 0.39382799999999996,\n", - " 0.39482799999999996,\n", - " 0.396023,\n", - " 0.397221,\n", - " 0.39842000000000005,\n", - " 0.39942,\n", - " 0.40061399999999997,\n", - " 0.401807,\n", - " 0.403003,\n", - " 0.404198,\n", - " 0.405384,\n", - " 0.40657400000000005,\n", - " 0.407575,\n", - " 0.408582,\n", - " 0.409597,\n", - " 0.41062200000000004,\n", - " 0.411643,\n", - " 0.412657,\n", - " 0.413677,\n", - " 0.41469900000000004,\n", - " 0.41571600000000003,\n", - " 0.41672800000000004,\n", - " 0.417742,\n", - " 0.41875799999999996,\n", - " 0.419772,\n", - " 0.420786,\n", - " 0.421801,\n", - " 0.422819,\n", - " 0.42384,\n", - " 0.424863,\n", - " 0.425894,\n", - " 0.426929,\n", - " 0.427968,\n", - " 0.429013,\n", - " 0.430059,\n", - " 0.431101,\n", - " 0.432139,\n", - " 0.433182,\n", - " 0.434225,\n", - " 0.435274,\n", - " 0.43632400000000005,\n", - " 0.437376,\n", - " 0.438433,\n", - " 0.4395,\n", - " 0.440574,\n", - " 0.441651,\n", - " 0.442724,\n", - " 0.44379,\n", - " 0.444853,\n", - " 0.445923,\n", - " 0.447003,\n", - " 0.44808800000000004,\n", - " 0.449168,\n", - " 0.450248,\n", - " 0.451325,\n", - " 0.452399,\n", - " 0.453483,\n", - " 0.45457600000000004,\n", - " 0.455667,\n", - " 0.45676,\n", - " 0.457857,\n", - " 0.458957,\n", - " 0.46005900000000005,\n", - " 0.461164,\n", - " 0.46227,\n", - " 0.463382,\n", - " 0.46449599999999996,\n", - " 0.465614,\n", - " 0.466729,\n", - " 0.467835,\n", - " 0.468944,\n", - " 0.470055,\n", - " 0.47116800000000003,\n", - " 0.47227600000000003,\n", - " 0.473382,\n", - " 0.474491,\n", - " 0.475601,\n", - " 0.476711,\n", - " 0.47782600000000003,\n", - " 0.47894600000000004,\n", - " 0.48007299999999997,\n", - " 0.481204,\n", - " 0.482331,\n", - " 0.48345,\n", - " 0.484559,\n", - " 0.485674,\n", - " 0.486798,\n", - " 0.487935,\n", - " 0.48907900000000004,\n", - " 0.49021800000000004,\n", - " 0.491359,\n", - " 0.492499,\n", - " 0.49363799999999997,\n", - " 0.49478,\n", - " 0.49592200000000003,\n", - " 0.497066,\n", - " 0.49821,\n", - " 0.499349,\n", - " 0.500486,\n", - " 0.501626,\n", - " 0.502774,\n", - " 0.503929,\n", - " 0.5050830000000001,\n", - " 0.506237,\n", - " 0.507391,\n", - " 0.508543,\n", - " 0.509699,\n", - " 0.5108550000000001,\n", - " 0.512009,\n", - " 0.51317,\n", - " 0.514334,\n", - " 0.5155,\n", - " 0.516663,\n", - " 0.5178360000000001,\n", - " 0.519009,\n", - " 0.520181,\n", - " 0.521361,\n", - " 0.522543,\n", - " 0.523721,\n", - " 0.5248980000000001,\n", - " 0.526073,\n", - " 0.527247,\n", - " 0.528424,\n", - " 0.52961,\n", - " 0.5307970000000001,\n", - " 0.531981,\n", - " 0.533164,\n", - " 0.53435,\n", - " 0.535537,\n", - " 0.536729,\n", - " 0.537928,\n", - " 0.539125,\n", - " 0.540325,\n", - " 0.5415209999999999,\n", - " 0.5427179999999999,\n", - " 0.543919,\n", - " 0.545122,\n", - " 0.546326,\n", - " 0.547535,\n", - " 0.548745,\n", - " 0.549952,\n", - " 0.55116,\n", - " 0.552377,\n", - " 0.553601,\n", - " 0.55483,\n", - " 0.55606,\n", - " 0.557289,\n", - " 0.558519,\n", - " 0.5597530000000001,\n", - " 0.5609890000000001,\n", - " 0.562219,\n", - " 0.5634560000000001,\n", - " 0.564699,\n", - " 0.565936,\n", - " 0.567177,\n", - " 0.568424,\n", - " 0.569667,\n", - " 0.570911,\n", - " 0.572155,\n", - " 0.573396,\n", - " 0.574641,\n", - " 0.575886,\n", - " 0.577126,\n", - " 0.57837,\n", - " 0.579615,\n", - " 0.580861,\n", - " 0.58211,\n", - " 0.5833579999999999,\n", - " 0.584605,\n", - " 0.585852,\n", - " 0.587091,\n", - " 0.5883339999999999,\n", - " 0.5893339999999999,\n", - " 0.590336,\n", - " 0.5913400000000001,\n", - " 0.592342,\n", - " 0.5933440000000001,\n", - " 0.5943440000000001,\n", - " 0.595346,\n", - " 0.596352,\n", - " 0.597364,\n", - " 0.598379,\n", - " 0.5993890000000001,\n", - " 0.6003970000000001,\n", - " 0.601401,\n", - " 0.602405,\n", - " 0.603414,\n", - " 0.60442,\n", - " 0.60542,\n", - " 0.606422,\n", - " 0.607428,\n", - " 0.6084360000000001,\n", - " 0.609448,\n", - " 0.610459,\n", - " 0.6114769999999999,\n", - " 0.612495,\n", - " 0.61351,\n", - " 0.6145280000000001,\n", - " 0.6155510000000001,\n", - " 0.616577,\n", - " 0.6176,\n", - " 0.618623,\n", - " 0.619649,\n", - " 0.620672,\n", - " 0.621698,\n", - " 0.6227229999999999,\n", - " 0.6237480000000001,\n", - " 0.6247780000000001,\n", - " 0.625811,\n", - " 0.626854,\n", - " 0.627893,\n", - " 0.628932,\n", - " 0.6299750000000001,\n", - " 0.631016,\n", - " 0.6320549999999999,\n", - " 0.63309,\n", - " 0.634129,\n", - " 0.63517,\n", - " 0.636213,\n", - " 0.6372580000000001,\n", - " 0.6383030000000001,\n", - " 0.6393500000000001,\n", - " 0.640389,\n", - " 0.641428,\n", - " 0.642473,\n", - " 0.64352,\n", - " 0.644569,\n", - " 0.64562,\n", - " 0.646672,\n", - " 0.647725,\n", - " 0.648768,\n", - " 0.649809,\n", - " 0.650852,\n", - " 0.651901,\n", - " 0.6529539999999999,\n", - " 0.654006,\n", - " 0.655057,\n", - " 0.656105,\n", - " 0.65716,\n", - " 0.6582210000000001,\n", - " 0.6592790000000001,\n", - " 0.66034,\n", - " 0.661398,\n", - " 0.6624589999999999,\n", - " 0.663519,\n", - " 0.6645800000000001,\n", - " 0.6656409999999999,\n", - " 0.6666989999999999,\n", - " 0.667752,\n", - " 0.668805,\n", - " 0.669861,\n", - " 0.670924,\n", - " 0.671986,\n", - " 0.6730430000000001,\n", - " 0.674103,\n", - " 0.6751699999999999,\n", - " 0.676234,\n", - " 0.677293,\n", - " 0.678348,\n", - " 0.679404,\n", - " 0.680471,\n", - " 0.6815410000000001,\n", - " 0.682611,\n", - " 0.683685,\n", - " 0.684762,\n", - " 0.685838,\n", - " 0.686914,\n", - " 0.687986,\n", - " 0.6890620000000001,\n", - " 0.690141,\n", - " 0.691221,\n", - " 0.692303,\n", - " 0.693389,\n", - " 0.694474,\n", - " 0.695557,\n", - " 0.696637,\n", - " 0.6977190000000001,\n", - " 0.698801,\n", - " 0.699883,\n", - " 0.7009690000000001,\n", - " 0.702055,\n", - " 0.703142,\n", - " 0.704236,\n", - " 0.70533,\n", - " 0.7064260000000001,\n", - " 0.707519,\n", - " 0.708613,\n", - " 0.7097129999999999,\n", - " 0.710818,\n", - " 0.71192,\n", - " 0.7130230000000001,\n", - " 0.714125,\n", - " 0.715228,\n", - " 0.71633,\n", - " 0.7174320000000001,\n", - " 0.718527,\n", - " 0.7196250000000001,\n", - " 0.720726,\n", - " 0.721836,\n", - " 0.722943,\n", - " 0.724047,\n", - " 0.725148,\n", - " 0.726256,\n", - " 0.727373,\n", - " 0.728492,\n", - " 0.729611,\n", - " 0.73073,\n", - " 0.731851,\n", - " 0.732974,\n", - " 0.734094,\n", - " 0.735207,\n", - " 0.7363200000000001,\n", - " 0.7374299999999999,\n", - " 0.7385410000000001,\n", - " 0.7396520000000001,\n", - " 0.7407670000000001,\n", - " 0.741887,\n", - " 0.7430059999999999,\n", - " 0.744121,\n", - " 0.745236,\n", - " 0.7463529999999999,\n", - " 0.747469,\n", - " 0.7485839999999999,\n", - " 0.749703,\n", - " 0.750832,\n", - " 0.7519710000000001,\n", - " 0.753105,\n", - " 0.754236,\n", - " 0.7553650000000001,\n", - " 0.756496,\n", - " 0.7576350000000001,\n", - " 0.758773,\n", - " 0.759914,\n", - " 0.761051,\n", - " 0.762189,\n", - " 0.7633260000000001,\n", - " 0.7644610000000001,\n", - " 0.76559,\n", - " 0.766721,\n", - " 0.7678550000000001,\n", - " 0.7689940000000001,\n", - " 0.770135,\n", - " 0.771279,\n", - " 0.7724260000000001,\n", - " 0.773572,\n", - " 0.774721,\n", - " 0.775873,\n", - " 0.777021,\n", - " 0.77817,\n", - " 0.779324,\n", - " 0.7804840000000001,\n", - " 0.781644,\n", - " 0.7828010000000001,\n", - " 0.7839550000000001,\n", - " 0.785107,\n", - " 0.786258,\n", - " 0.787412,\n", - " 0.7885700000000001,\n", - " 0.789732,\n", - " 0.7908959999999999,\n", - " 0.79206,\n", - " 0.7932279999999999,\n", - " 0.794396,\n", - " 0.795566,\n", - " 0.79674,\n", - " 0.797913,\n", - " 0.799085,\n", - " 0.800257,\n", - " 0.8014330000000001,\n", - " 0.8026150000000001,\n", - " 0.803794,\n", - " 0.804976,\n", - " 0.8061630000000001,\n", - " 0.807355,\n", - " 0.8085399999999999,\n", - " 0.8097179999999999,\n", - " 0.810898,\n", - " 0.8120850000000001,\n", - " 0.813277,\n", - " 0.814466,\n", - " 0.815652,\n", - " 0.8168390000000001,\n", - " 0.818035,\n", - " 0.819232,\n", - " 0.820429,\n", - " 0.8216319999999999,\n", - " 0.8228390000000001,\n", - " 0.82405,\n", - " 0.825257,\n", - " 0.826462,\n", - " 0.827671,\n", - " 0.8288780000000001,\n", - " 0.830083,\n", - " 0.831292,\n", - " 0.832505,\n", - " 0.83372,\n", - " 0.834937,\n", - " 0.836156,\n", - " 0.8373700000000001,\n", - " 0.838585,\n", - " 0.839808,\n", - " 0.841029,\n", - " 0.842245,\n", - " 0.843466,\n", - " 0.8446910000000001,\n", - " 0.845921,\n", - " 0.847152,\n", - " 0.848376,\n", - " 0.849597,\n", - " 0.850818,\n", - " 0.852036,\n", - " 0.8532569999999999,\n", - " 0.85448,\n", - " 0.855704,\n", - " 0.8569249999999999,\n", - " 0.858144,\n", - " 0.859367,\n", - " 0.860595,\n", - " 0.8618260000000001,\n", - " 0.8630599999999999,\n", - " 0.864294,\n", - " 0.865525,\n", - " 0.866751,\n", - " 0.8679800000000001,\n", - " 0.869206,\n", - " 0.870435,\n", - " 0.8716630000000001,\n", - " 0.87289,\n", - " 0.87412,\n", - " 0.8753569999999999,\n", - " 0.876599,\n", - " 0.877837,\n", - " 0.879068,\n", - " 0.880294,\n", - " 0.8815230000000001,\n", - " 0.882749,\n", - " 0.88398,\n", - " 0.8852140000000001,\n", - " 0.886445,\n", - " 0.887673,\n", - " 0.8889020000000001,\n", - " 0.89013,\n", - " 0.891359,\n", - " 0.892583,\n", - " 0.893812,\n", - " 0.895044,\n", - " 0.8962730000000001,\n", - " 0.897499,\n", - " 0.898732,\n", - " 0.8999600000000001,\n", - " 0.901183,\n", - " 0.902411,\n", - " 0.903648,\n", - " 0.904882,\n", - " 0.906109,\n", - " 0.9073289999999999,\n", - " 0.908552,\n", - " 0.909785,\n", - " 0.911025,\n", - " 0.912267,\n", - " 0.913511,\n", - " 0.9147569999999999,\n", - " 0.916001,\n", - " 0.917245,\n", - " 0.918494,\n", - " 0.919744,\n", - " 0.92099,\n", - " 0.9222340000000001,\n", - " 0.923476,\n", - " 0.924716,\n", - " 0.925956,\n", - " 0.927203,\n", - " 0.928454,\n", - " 0.929706,\n", - " 0.930954,\n", - " 0.931892,\n", - " 0.933146,\n", - " 0.934396,\n", - " 0.9356420000000001,\n", - " 0.936886,\n", - " 0.9381280000000001,\n", - " 0.939369,\n", - " 0.940615,\n", - " 0.9418630000000001,\n", - " 0.943111,\n", - " 0.944357,\n", - " 0.945603,\n", - " 0.946847,\n", - " 0.948097,\n", - " 0.9493510000000001,\n", - " 0.950603,\n", - " 0.951851,\n", - " 0.953101,\n", - " 0.954357,\n", - " 0.955613,\n", - " 0.9568650000000001,\n", - " 0.958122,\n", - " 0.959382,\n", - " 0.9606380000000001,\n", - " 0.961896,\n", - " 0.963156,\n", - " 0.9644170000000001,\n", - " 0.965675,\n", - " 0.966933,\n", - " 0.968197,\n", - " 0.969458,\n", - " 0.970724,\n", - " 0.971992,\n", - " 0.973259,\n", - " 0.974527,\n", - " 0.975794,\n", - " 0.977062,\n", - " 0.9783310000000001,\n", - " 0.979607,\n", - " 0.9808840000000001,\n", - " 0.982159,\n", - " 0.983433,\n", - " 0.984708,\n", - " 0.985986,\n", - " 0.9872650000000001,\n", - " 0.9885499999999999,\n", - " 0.989831,\n", - " 0.991111,\n", - " 0.99239,\n", - " 0.993669,\n", - " 0.994951,\n", - " 0.996236,\n", - " 0.997527,\n", - " 0.99882,\n", - " 1.00011,\n", - " 1.0014,\n", - " 1.00269,\n", - " 1.00398,\n", - " 1.00527,\n", - " 1.00657,\n", - " 1.00787,\n", - " 1.00917,\n", - " 1.01047,\n", - " 1.01177,\n", - " 1.0130700000000001,\n", - " 1.01437,\n", - " 1.01567,\n", - " 1.0169700000000002,\n", - " 1.01828,\n", - " 1.01959,\n", - " 1.02089,\n", - " 1.0221900000000002,\n", - " 1.0235,\n", - " 1.0248,\n", - " 1.0261099999999999,\n", - " 1.02742,\n", - " 1.0287300000000001,\n", - " 1.03004,\n", - " 1.0313599999999998,\n", - " 1.0326700000000002,\n", - " 1.0339800000000001,\n", - " 1.03529,\n", - " 1.03661,\n", - " 1.03793,\n", - " 1.03924,\n", - " 1.04055,\n", - " 1.0418699999999999,\n", - " 1.04318,\n", - " 1.0445,\n", - " 1.04581,\n", - " 1.0471300000000001,\n", - " 1.04845,\n", - " 1.04977,\n", - " 1.0510899999999999,\n", - " 1.05241,\n", - " 1.05372,\n", - " 1.05504,\n", - " 1.0563699999999998,\n", - " 1.0577,\n", - " 1.05902,\n", - " 1.06035,\n", - " 1.0616700000000001,\n", - " 1.06298,\n", - " 1.0643,\n", - " 1.06562,\n", - " 1.06695,\n", - " 1.06828,\n", - " 1.06928,\n", - " 1.07028,\n", - " 1.07128,\n", - " 1.07229,\n", - " 1.07329,\n", - " 1.07462,\n", - " 1.07562,\n", - " 1.0766300000000002,\n", - " 1.07763,\n", - " 1.0786300000000002,\n", - " 1.0796400000000002,\n", - " 1.08064,\n", - " 1.0816400000000002,\n", - " 1.0826500000000001,\n", - " 1.0836500000000002,\n", - " 1.0846600000000002,\n", - " 1.08566,\n", - " 1.08667,\n", - " 1.0876700000000001,\n", - " 1.08868,\n", - " 1.08969,\n", - " 1.0907,\n", - " 1.09172,\n", - " 1.09273,\n", - " 1.09374,\n", - " 1.09476,\n", - " 1.09577,\n", - " 1.09678,\n", - " 1.0977999999999999,\n", - " 1.09882,\n", - " 1.09985,\n", - " 1.10087,\n", - " 1.10189,\n", - " 1.10291,\n", - " 1.10393,\n", - " 1.10494,\n", - " 1.10596,\n", - " 1.10698,\n", - " 1.108,\n", - " 1.10901,\n", - " 1.11003,\n", - " 1.1110499999999999,\n", - " 1.11207,\n", - " 1.1130799999999998,\n", - " 1.11409,\n", - " 1.1151099999999998,\n", - " 1.11612,\n", - " 1.11714,\n", - " 1.11816,\n", - " 1.11917,\n", - " 1.12019,\n", - " 1.1212,\n", - " 1.1222100000000002,\n", - " 1.12321,\n", - " 1.12422,\n", - " 1.12523,\n", - " 1.1262400000000001,\n", - " 1.12726,\n", - " 1.12828,\n", - " 1.1293,\n", - " 1.13032,\n", - " 1.13133,\n", - " 1.13235,\n", - " 1.13337,\n", - " 1.1343900000000002,\n", - " 1.13541,\n", - " 1.13643,\n", - " 1.13745,\n", - " 1.13846,\n", - " 1.13948,\n", - " 1.1405,\n", - " 1.14152,\n", - " 1.14254,\n", - " 1.14356,\n", - " 1.14458,\n", - " 1.1456,\n", - " 1.14663,\n", - " 1.1476600000000001,\n", - " 1.14869,\n", - " 1.14972,\n", - " 1.15074,\n", - " 1.1517600000000001,\n", - " 1.15279,\n", - " 1.15382,\n", - " 1.15485,\n", - " 1.1558800000000002,\n", - " 1.15691,\n", - " 1.1579300000000001,\n", - " 1.15896,\n", - " 1.15999,\n", - " 1.16102,\n", - " 1.16205,\n", - " 1.16308,\n", - " 1.1641,\n", - " 1.1651300000000002,\n", - " 1.16616,\n", - " 1.16718,\n", - " 1.1682000000000001,\n", - " 1.1692200000000001,\n", - " 1.17025,\n", - " 1.17127,\n", - " 1.17231,\n", - " 1.17334,\n", - " 1.1743800000000002,\n", - " 1.17541,\n", - " 1.17645,\n", - " 1.17748,\n", - " 1.17851,\n", - " 1.1795499999999999,\n", - " 1.18059,\n", - " 1.18162,\n", - " 1.18266,\n", - " 1.1837,\n", - " 1.1847400000000001,\n", - " 1.18578,\n", - " 1.18682,\n", - " 1.18787,\n", - " ...]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" + "ename": "AttributeError", + "evalue": "'list' object has no attribute 'convert_to'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[14], line 7\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mOriginal unit:\u001b[39m\u001b[38;5;124m\"\u001b[39m, item\u001b[38;5;241m.\u001b[39mhdf5\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mStandardkraft\u001b[39m\u001b[38;5;124m\"\u001b[39m)\u001b[38;5;241m.\u001b[39mget_unit())\n\u001b[0;32m 6\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mNow the converted data column in kN\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m----> 7\u001b[0m \u001b[43mitem\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcustom_properties\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcontent\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mVorkraft\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconvert_to\u001b[49m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkPa\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 8\u001b[0m item\u001b[38;5;241m.\u001b[39mhdf5\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mStandardkraft\u001b[39m\u001b[38;5;124m\"\u001b[39m)\u001b[38;5;241m.\u001b[39mconvert_to(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkN\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[1;31mAttributeError\u001b[0m: 'list' object has no attribute 'convert_to'" + ] } ], "source": [ - "uuid = \"e49f1e17-f62d-46b2-b23f-72f1642a26ce\"\n", + "uuid = \"8d6b034e-efb4-427a-a0d3-666e62762cba\"\n", "item = dsms[uuid]\n", "\n", "print(\"Original unit:\", item.hdf5.get(\"Standardkraft\").get_unit())\n", "\n", "print(\"\\n\\nNow the converted data column in kN\")\n", - "item.hdf5.get(\"Standardkraft\").convert_to(\"kN\")" + "item.hdf5.get(\"Standardkraft\").convert_to(\"kN\")\n" ] } ], From b2aa1b07730537bb694268bf0a759fe691cf7d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 10 Apr 2024 11:49:37 +0200 Subject: [PATCH 011/114] update example notebook --- examples/convert.py | 11 + examples/unit_conversion.ipynb | 1023 +++++++++++++++++++++++++++++++- 2 files changed, 1022 insertions(+), 12 deletions(-) create mode 100644 examples/convert.py diff --git a/examples/convert.py b/examples/convert.py new file mode 100644 index 0000000..8848281 --- /dev/null +++ b/examples/convert.py @@ -0,0 +1,11 @@ +from dsms import DSMS, KItem + +dsms = DSMS(host_url="https://stahldigital.materials-data.space", env="../.env") + +uuid = "8d6b034e-efb4-427a-a0d3-666e62762cba" +item = dsms[uuid] + + +item.hdf5.get("Standardkraft").convert_to("kN") + +item.custom_properties.content.Projektnummer.convert_to("kPa") diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index a502b51..5c8cbdc 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -26,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -206,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -220,15 +220,1014 @@ ] }, { - "ename": "AttributeError", - "evalue": "'list' object has no attribute 'convert_to'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[14], line 7\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mOriginal unit:\u001b[39m\u001b[38;5;124m\"\u001b[39m, item\u001b[38;5;241m.\u001b[39mhdf5\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mStandardkraft\u001b[39m\u001b[38;5;124m\"\u001b[39m)\u001b[38;5;241m.\u001b[39mget_unit())\n\u001b[0;32m 6\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mNow the converted data column in kN\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m----> 7\u001b[0m \u001b[43mitem\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcustom_properties\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcontent\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mVorkraft\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconvert_to\u001b[49m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkPa\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 8\u001b[0m item\u001b[38;5;241m.\u001b[39mhdf5\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mStandardkraft\u001b[39m\u001b[38;5;124m\"\u001b[39m)\u001b[38;5;241m.\u001b[39mconvert_to(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkN\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[1;31mAttributeError\u001b[0m: 'list' object has no attribute 'convert_to'" - ] + "data": { + "text/plain": [ + "[0.0819691,\n", + " 0.0820348,\n", + " 0.0820854,\n", + " 0.0820619,\n", + " 0.0820386,\n", + " 0.0820924,\n", + " 0.0821014,\n", + " 0.08210890000000001,\n", + " 0.0821103,\n", + " 0.0822442,\n", + " 0.0827859,\n", + " 0.08363190000000001,\n", + " 0.0846336,\n", + " 0.0856447,\n", + " 0.0866605,\n", + " 0.0876807,\n", + " 0.0886995,\n", + " 0.0897311,\n", + " 0.0907341,\n", + " 0.0917502,\n", + " 0.09278,\n", + " 0.09379269999999999,\n", + " 0.0948254,\n", + " 0.09582760000000001,\n", + " 0.096832,\n", + " 0.09786990000000001,\n", + " 0.0988997,\n", + " 0.0999365,\n", + " 0.100964,\n", + " 0.101998,\n", + " 0.10302800000000001,\n", + " 0.104029,\n", + " 0.105075,\n", + " 0.106114,\n", + " 0.10714,\n", + " 0.10819100000000001,\n", + " 0.109229,\n", + " 0.110281,\n", + " 0.111333,\n", + " 0.112383,\n", + " 0.113418,\n", + " 0.11445699999999999,\n", + " 0.11551399999999999,\n", + " 0.116562,\n", + " 0.11763,\n", + " 0.1187,\n", + " 0.119718,\n", + " 0.12075100000000001,\n", + " 0.12181600000000001,\n", + " 0.12288500000000001,\n", + " 0.123891,\n", + " 0.124923,\n", + " 0.125973,\n", + " 0.127044,\n", + " 0.12804400000000002,\n", + " 0.129065,\n", + " 0.130079,\n", + " 0.131119,\n", + " 0.13218000000000002,\n", + " 0.13324100000000003,\n", + " 0.134311,\n", + " 0.135313,\n", + " 0.13631800000000002,\n", + " 0.137338,\n", + " 0.13836099999999998,\n", + " 0.139367,\n", + " 0.140405,\n", + " 0.141445,\n", + " 0.14248,\n", + " 0.143527,\n", + " 0.144568,\n", + " 0.14561500000000002,\n", + " 0.14665799999999998,\n", + " 0.14772100000000002,\n", + " 0.148785,\n", + " 0.149847,\n", + " 0.150923,\n", + " 0.151993,\n", + " 0.153091,\n", + " 0.15418,\n", + " 0.15527000000000002,\n", + " 0.156367,\n", + " 0.15746700000000002,\n", + " 0.158492,\n", + " 0.159524,\n", + " 0.160537,\n", + " 0.161571,\n", + " 0.16262000000000001,\n", + " 0.16367400000000001,\n", + " 0.164722,\n", + " 0.165773,\n", + " 0.16685400000000003,\n", + " 0.167942,\n", + " 0.16902799999999998,\n", + " 0.170118,\n", + " 0.171206,\n", + " 0.172313,\n", + " 0.17341800000000002,\n", + " 0.174435,\n", + " 0.175474,\n", + " 0.17650100000000002,\n", + " 0.17753200000000002,\n", + " 0.178574,\n", + " 0.179602,\n", + " 0.180634,\n", + " 0.18166800000000002,\n", + " 0.18272300000000002,\n", + " 0.18377500000000002,\n", + " 0.18484,\n", + " 0.185898,\n", + " 0.18697,\n", + " 0.188062,\n", + " 0.189148,\n", + " 0.19023500000000002,\n", + " 0.19131700000000001,\n", + " 0.19243000000000002,\n", + " 0.193545,\n", + " 0.194664,\n", + " 0.195666,\n", + " 0.196791,\n", + " 0.197792,\n", + " 0.19891399999999998,\n", + " 0.199915,\n", + " 0.200917,\n", + " 0.202035,\n", + " 0.203046,\n", + " 0.204075,\n", + " 0.2051,\n", + " 0.206147,\n", + " 0.207202,\n", + " 0.208254,\n", + " 0.20929499999999998,\n", + " 0.21035800000000002,\n", + " 0.211423,\n", + " 0.212478,\n", + " 0.213535,\n", + " 0.214605,\n", + " 0.215678,\n", + " 0.216763,\n", + " 0.217838,\n", + " 0.21890600000000002,\n", + " 0.219975,\n", + " 0.221052,\n", + " 0.22215000000000001,\n", + " 0.223251,\n", + " 0.224351,\n", + " 0.225456,\n", + " 0.226578,\n", + " 0.227708,\n", + " 0.22871100000000003,\n", + " 0.229712,\n", + " 0.230716,\n", + " 0.231718,\n", + " 0.23286,\n", + " 0.233988,\n", + " 0.23512200000000003,\n", + " 0.236136,\n", + " 0.23715,\n", + " 0.238162,\n", + " 0.23919300000000002,\n", + " 0.240231,\n", + " 0.241262,\n", + " 0.242287,\n", + " 0.243316,\n", + " 0.244346,\n", + " 0.245377,\n", + " 0.246418,\n", + " 0.247471,\n", + " 0.248542,\n", + " 0.249613,\n", + " 0.250686,\n", + " 0.251741,\n", + " 0.252801,\n", + " 0.253871,\n", + " 0.254951,\n", + " 0.25602800000000003,\n", + " 0.25711700000000004,\n", + " 0.25819600000000004,\n", + " 0.259282,\n", + " 0.26036200000000004,\n", + " 0.261452,\n", + " 0.262552,\n", + " 0.263659,\n", + " 0.264779,\n", + " 0.265905,\n", + " 0.267023,\n", + " 0.26813200000000004,\n", + " 0.26924400000000004,\n", + " 0.270358,\n", + " 0.27148300000000003,\n", + " 0.27261900000000006,\n", + " 0.27376100000000003,\n", + " 0.27490499999999995,\n", + " 0.27606200000000003,\n", + " 0.277214,\n", + " 0.27835899999999997,\n", + " 0.27950400000000003,\n", + " 0.280652,\n", + " 0.2818,\n", + " 0.282957,\n", + " 0.283963,\n", + " 0.284974,\n", + " 0.28598,\n", + " 0.287146,\n", + " 0.288148,\n", + " 0.289151,\n", + " 0.290171,\n", + " 0.29119,\n", + " 0.29221199999999997,\n", + " 0.293236,\n", + " 0.294262,\n", + " 0.295286,\n", + " 0.296313,\n", + " 0.297333,\n", + " 0.298352,\n", + " 0.29936900000000005,\n", + " 0.300394,\n", + " 0.30143200000000003,\n", + " 0.30248,\n", + " 0.303524,\n", + " 0.304564,\n", + " 0.305611,\n", + " 0.30666000000000004,\n", + " 0.30770600000000004,\n", + " 0.308751,\n", + " 0.309799,\n", + " 0.310849,\n", + " 0.311906,\n", + " 0.31297,\n", + " 0.314039,\n", + " 0.315106,\n", + " 0.316166,\n", + " 0.317229,\n", + " 0.318288,\n", + " 0.319349,\n", + " 0.320409,\n", + " 0.321476,\n", + " 0.322549,\n", + " 0.32362,\n", + " 0.32469,\n", + " 0.325758,\n", + " 0.326818,\n", + " 0.32787900000000003,\n", + " 0.32895100000000005,\n", + " 0.330022,\n", + " 0.331087,\n", + " 0.33216,\n", + " 0.333237,\n", + " 0.334306,\n", + " 0.335384,\n", + " 0.336463,\n", + " 0.337534,\n", + " 0.338606,\n", + " 0.339687,\n", + " 0.340767,\n", + " 0.34185000000000004,\n", + " 0.342943,\n", + " 0.344037,\n", + " 0.345123,\n", + " 0.346212,\n", + " 0.34730500000000003,\n", + " 0.348402,\n", + " 0.349494,\n", + " 0.350592,\n", + " 0.35170100000000004,\n", + " 0.352801,\n", + " 0.35390499999999997,\n", + " 0.355011,\n", + " 0.356119,\n", + " 0.357233,\n", + " 0.35834699999999997,\n", + " 0.359462,\n", + " 0.360576,\n", + " 0.361702,\n", + " 0.362843,\n", + " 0.363991,\n", + " 0.365137,\n", + " 0.366287,\n", + " 0.367437,\n", + " 0.368587,\n", + " 0.369739,\n", + " 0.370897,\n", + " 0.37205200000000005,\n", + " 0.373214,\n", + " 0.374373,\n", + " 0.37553800000000004,\n", + " 0.376709,\n", + " 0.377877,\n", + " 0.379041,\n", + " 0.380214,\n", + " 0.38138299999999997,\n", + " 0.382556,\n", + " 0.38373399999999996,\n", + " 0.384919,\n", + " 0.386101,\n", + " 0.387281,\n", + " 0.388459,\n", + " 0.38963400000000004,\n", + " 0.390823,\n", + " 0.391824,\n", + " 0.392824,\n", + " 0.39382799999999996,\n", + " 0.39482799999999996,\n", + " 0.396023,\n", + " 0.397221,\n", + " 0.39842000000000005,\n", + " 0.39942,\n", + " 0.40061399999999997,\n", + " 0.401807,\n", + " 0.403003,\n", + " 0.404198,\n", + " 0.405384,\n", + " 0.40657400000000005,\n", + " 0.407575,\n", + " 0.408582,\n", + " 0.409597,\n", + " 0.41062200000000004,\n", + " 0.411643,\n", + " 0.412657,\n", + " 0.413677,\n", + " 0.41469900000000004,\n", + " 0.41571600000000003,\n", + " 0.41672800000000004,\n", + " 0.417742,\n", + " 0.41875799999999996,\n", + " 0.419772,\n", + " 0.420786,\n", + " 0.421801,\n", + " 0.422819,\n", + " 0.42384,\n", + " 0.424863,\n", + " 0.425894,\n", + " 0.426929,\n", + " 0.427968,\n", + " 0.429013,\n", + " 0.430059,\n", + " 0.431101,\n", + " 0.432139,\n", + " 0.433182,\n", + " 0.434225,\n", + " 0.435274,\n", + " 0.43632400000000005,\n", + " 0.437376,\n", + " 0.438433,\n", + " 0.4395,\n", + " 0.440574,\n", + " 0.441651,\n", + " 0.442724,\n", + " 0.44379,\n", + " 0.444853,\n", + " 0.445923,\n", + " 0.447003,\n", + " 0.44808800000000004,\n", + " 0.449168,\n", + " 0.450248,\n", + " 0.451325,\n", + " 0.452399,\n", + " 0.453483,\n", + " 0.45457600000000004,\n", + " 0.455667,\n", + " 0.45676,\n", + " 0.457857,\n", + " 0.458957,\n", + " 0.46005900000000005,\n", + " 0.461164,\n", + " 0.46227,\n", + " 0.463382,\n", + " 0.46449599999999996,\n", + " 0.465614,\n", + " 0.466729,\n", + " 0.467835,\n", + " 0.468944,\n", + " 0.470055,\n", + " 0.47116800000000003,\n", + " 0.47227600000000003,\n", + " 0.473382,\n", + " 0.474491,\n", + " 0.475601,\n", + " 0.476711,\n", + " 0.47782600000000003,\n", + " 0.47894600000000004,\n", + " 0.48007299999999997,\n", + " 0.481204,\n", + " 0.482331,\n", + " 0.48345,\n", + " 0.484559,\n", + " 0.485674,\n", + " 0.486798,\n", + " 0.487935,\n", + " 0.48907900000000004,\n", + " 0.49021800000000004,\n", + " 0.491359,\n", + " 0.492499,\n", + " 0.49363799999999997,\n", + " 0.49478,\n", + " 0.49592200000000003,\n", + " 0.497066,\n", + " 0.49821,\n", + " 0.499349,\n", + " 0.500486,\n", + " 0.501626,\n", + " 0.502774,\n", + " 0.503929,\n", + " 0.5050830000000001,\n", + " 0.506237,\n", + " 0.507391,\n", + " 0.508543,\n", + " 0.509699,\n", + " 0.5108550000000001,\n", + " 0.512009,\n", + " 0.51317,\n", + " 0.514334,\n", + " 0.5155,\n", + " 0.516663,\n", + " 0.5178360000000001,\n", + " 0.519009,\n", + " 0.520181,\n", + " 0.521361,\n", + " 0.522543,\n", + " 0.523721,\n", + " 0.5248980000000001,\n", + " 0.526073,\n", + " 0.527247,\n", + " 0.528424,\n", + " 0.52961,\n", + " 0.5307970000000001,\n", + " 0.531981,\n", + " 0.533164,\n", + " 0.53435,\n", + " 0.535537,\n", + " 0.536729,\n", + " 0.537928,\n", + " 0.539125,\n", + " 0.540325,\n", + " 0.5415209999999999,\n", + " 0.5427179999999999,\n", + " 0.543919,\n", + " 0.545122,\n", + " 0.546326,\n", + " 0.547535,\n", + " 0.548745,\n", + " 0.549952,\n", + " 0.55116,\n", + " 0.552377,\n", + " 0.553601,\n", + " 0.55483,\n", + " 0.55606,\n", + " 0.557289,\n", + " 0.558519,\n", + " 0.5597530000000001,\n", + " 0.5609890000000001,\n", + " 0.562219,\n", + " 0.5634560000000001,\n", + " 0.564699,\n", + " 0.565936,\n", + " 0.567177,\n", + " 0.568424,\n", + " 0.569667,\n", + " 0.570911,\n", + " 0.572155,\n", + " 0.573396,\n", + " 0.574641,\n", + " 0.575886,\n", + " 0.577126,\n", + " 0.57837,\n", + " 0.579615,\n", + " 0.580861,\n", + " 0.58211,\n", + " 0.5833579999999999,\n", + " 0.584605,\n", + " 0.585852,\n", + " 0.587091,\n", + " 0.5883339999999999,\n", + " 0.5893339999999999,\n", + " 0.590336,\n", + " 0.5913400000000001,\n", + " 0.592342,\n", + " 0.5933440000000001,\n", + " 0.5943440000000001,\n", + " 0.595346,\n", + " 0.596352,\n", + " 0.597364,\n", + " 0.598379,\n", + " 0.5993890000000001,\n", + " 0.6003970000000001,\n", + " 0.601401,\n", + " 0.602405,\n", + " 0.603414,\n", + " 0.60442,\n", + " 0.60542,\n", + " 0.606422,\n", + " 0.607428,\n", + " 0.6084360000000001,\n", + " 0.609448,\n", + " 0.610459,\n", + " 0.6114769999999999,\n", + " 0.612495,\n", + " 0.61351,\n", + " 0.6145280000000001,\n", + " 0.6155510000000001,\n", + " 0.616577,\n", + " 0.6176,\n", + " 0.618623,\n", + " 0.619649,\n", + " 0.620672,\n", + " 0.621698,\n", + " 0.6227229999999999,\n", + " 0.6237480000000001,\n", + " 0.6247780000000001,\n", + " 0.625811,\n", + " 0.626854,\n", + " 0.627893,\n", + " 0.628932,\n", + " 0.6299750000000001,\n", + " 0.631016,\n", + " 0.6320549999999999,\n", + " 0.63309,\n", + " 0.634129,\n", + " 0.63517,\n", + " 0.636213,\n", + " 0.6372580000000001,\n", + " 0.6383030000000001,\n", + " 0.6393500000000001,\n", + " 0.640389,\n", + " 0.641428,\n", + " 0.642473,\n", + " 0.64352,\n", + " 0.644569,\n", + " 0.64562,\n", + " 0.646672,\n", + " 0.647725,\n", + " 0.648768,\n", + " 0.649809,\n", + " 0.650852,\n", + " 0.651901,\n", + " 0.6529539999999999,\n", + " 0.654006,\n", + " 0.655057,\n", + " 0.656105,\n", + " 0.65716,\n", + " 0.6582210000000001,\n", + " 0.6592790000000001,\n", + " 0.66034,\n", + " 0.661398,\n", + " 0.6624589999999999,\n", + " 0.663519,\n", + " 0.6645800000000001,\n", + " 0.6656409999999999,\n", + " 0.6666989999999999,\n", + " 0.667752,\n", + " 0.668805,\n", + " 0.669861,\n", + " 0.670924,\n", + " 0.671986,\n", + " 0.6730430000000001,\n", + " 0.674103,\n", + " 0.6751699999999999,\n", + " 0.676234,\n", + " 0.677293,\n", + " 0.678348,\n", + " 0.679404,\n", + " 0.680471,\n", + " 0.6815410000000001,\n", + " 0.682611,\n", + " 0.683685,\n", + " 0.684762,\n", + " 0.685838,\n", + " 0.686914,\n", + " 0.687986,\n", + " 0.6890620000000001,\n", + " 0.690141,\n", + " 0.691221,\n", + " 0.692303,\n", + " 0.693389,\n", + " 0.694474,\n", + " 0.695557,\n", + " 0.696637,\n", + " 0.6977190000000001,\n", + " 0.698801,\n", + " 0.699883,\n", + " 0.7009690000000001,\n", + " 0.702055,\n", + " 0.703142,\n", + " 0.704236,\n", + " 0.70533,\n", + " 0.7064260000000001,\n", + " 0.707519,\n", + " 0.708613,\n", + " 0.7097129999999999,\n", + " 0.710818,\n", + " 0.71192,\n", + " 0.7130230000000001,\n", + " 0.714125,\n", + " 0.715228,\n", + " 0.71633,\n", + " 0.7174320000000001,\n", + " 0.718527,\n", + " 0.7196250000000001,\n", + " 0.720726,\n", + " 0.721836,\n", + " 0.722943,\n", + " 0.724047,\n", + " 0.725148,\n", + " 0.726256,\n", + " 0.727373,\n", + " 0.728492,\n", + " 0.729611,\n", + " 0.73073,\n", + " 0.731851,\n", + " 0.732974,\n", + " 0.734094,\n", + " 0.735207,\n", + " 0.7363200000000001,\n", + " 0.7374299999999999,\n", + " 0.7385410000000001,\n", + " 0.7396520000000001,\n", + " 0.7407670000000001,\n", + " 0.741887,\n", + " 0.7430059999999999,\n", + " 0.744121,\n", + " 0.745236,\n", + " 0.7463529999999999,\n", + " 0.747469,\n", + " 0.7485839999999999,\n", + " 0.749703,\n", + " 0.750832,\n", + " 0.7519710000000001,\n", + " 0.753105,\n", + " 0.754236,\n", + " 0.7553650000000001,\n", + " 0.756496,\n", + " 0.7576350000000001,\n", + " 0.758773,\n", + " 0.759914,\n", + " 0.761051,\n", + " 0.762189,\n", + " 0.7633260000000001,\n", + " 0.7644610000000001,\n", + " 0.76559,\n", + " 0.766721,\n", + " 0.7678550000000001,\n", + " 0.7689940000000001,\n", + " 0.770135,\n", + " 0.771279,\n", + " 0.7724260000000001,\n", + " 0.773572,\n", + " 0.774721,\n", + " 0.775873,\n", + " 0.777021,\n", + " 0.77817,\n", + " 0.779324,\n", + " 0.7804840000000001,\n", + " 0.781644,\n", + " 0.7828010000000001,\n", + " 0.7839550000000001,\n", + " 0.785107,\n", + " 0.786258,\n", + " 0.787412,\n", + " 0.7885700000000001,\n", + " 0.789732,\n", + " 0.7908959999999999,\n", + " 0.79206,\n", + " 0.7932279999999999,\n", + " 0.794396,\n", + " 0.795566,\n", + " 0.79674,\n", + " 0.797913,\n", + " 0.799085,\n", + " 0.800257,\n", + " 0.8014330000000001,\n", + " 0.8026150000000001,\n", + " 0.803794,\n", + " 0.804976,\n", + " 0.8061630000000001,\n", + " 0.807355,\n", + " 0.8085399999999999,\n", + " 0.8097179999999999,\n", + " 0.810898,\n", + " 0.8120850000000001,\n", + " 0.813277,\n", + " 0.814466,\n", + " 0.815652,\n", + " 0.8168390000000001,\n", + " 0.818035,\n", + " 0.819232,\n", + " 0.820429,\n", + " 0.8216319999999999,\n", + " 0.8228390000000001,\n", + " 0.82405,\n", + " 0.825257,\n", + " 0.826462,\n", + " 0.827671,\n", + " 0.8288780000000001,\n", + " 0.830083,\n", + " 0.831292,\n", + " 0.832505,\n", + " 0.83372,\n", + " 0.834937,\n", + " 0.836156,\n", + " 0.8373700000000001,\n", + " 0.838585,\n", + " 0.839808,\n", + " 0.841029,\n", + " 0.842245,\n", + " 0.843466,\n", + " 0.8446910000000001,\n", + " 0.845921,\n", + " 0.847152,\n", + " 0.848376,\n", + " 0.849597,\n", + " 0.850818,\n", + " 0.852036,\n", + " 0.8532569999999999,\n", + " 0.85448,\n", + " 0.855704,\n", + " 0.8569249999999999,\n", + " 0.858144,\n", + " 0.859367,\n", + " 0.860595,\n", + " 0.8618260000000001,\n", + " 0.8630599999999999,\n", + " 0.864294,\n", + " 0.865525,\n", + " 0.866751,\n", + " 0.8679800000000001,\n", + " 0.869206,\n", + " 0.870435,\n", + " 0.8716630000000001,\n", + " 0.87289,\n", + " 0.87412,\n", + " 0.8753569999999999,\n", + " 0.876599,\n", + " 0.877837,\n", + " 0.879068,\n", + " 0.880294,\n", + " 0.8815230000000001,\n", + " 0.882749,\n", + " 0.88398,\n", + " 0.8852140000000001,\n", + " 0.886445,\n", + " 0.887673,\n", + " 0.8889020000000001,\n", + " 0.89013,\n", + " 0.891359,\n", + " 0.892583,\n", + " 0.893812,\n", + " 0.895044,\n", + " 0.8962730000000001,\n", + " 0.897499,\n", + " 0.898732,\n", + " 0.8999600000000001,\n", + " 0.901183,\n", + " 0.902411,\n", + " 0.903648,\n", + " 0.904882,\n", + " 0.906109,\n", + " 0.9073289999999999,\n", + " 0.908552,\n", + " 0.909785,\n", + " 0.911025,\n", + " 0.912267,\n", + " 0.913511,\n", + " 0.9147569999999999,\n", + " 0.916001,\n", + " 0.917245,\n", + " 0.918494,\n", + " 0.919744,\n", + " 0.92099,\n", + " 0.9222340000000001,\n", + " 0.923476,\n", + " 0.924716,\n", + " 0.925956,\n", + " 0.927203,\n", + " 0.928454,\n", + " 0.929706,\n", + " 0.930954,\n", + " 0.931892,\n", + " 0.933146,\n", + " 0.934396,\n", + " 0.9356420000000001,\n", + " 0.936886,\n", + " 0.9381280000000001,\n", + " 0.939369,\n", + " 0.940615,\n", + " 0.9418630000000001,\n", + " 0.943111,\n", + " 0.944357,\n", + " 0.945603,\n", + " 0.946847,\n", + " 0.948097,\n", + " 0.9493510000000001,\n", + " 0.950603,\n", + " 0.951851,\n", + " 0.953101,\n", + " 0.954357,\n", + " 0.955613,\n", + " 0.9568650000000001,\n", + " 0.958122,\n", + " 0.959382,\n", + " 0.9606380000000001,\n", + " 0.961896,\n", + " 0.963156,\n", + " 0.9644170000000001,\n", + " 0.965675,\n", + " 0.966933,\n", + " 0.968197,\n", + " 0.969458,\n", + " 0.970724,\n", + " 0.971992,\n", + " 0.973259,\n", + " 0.974527,\n", + " 0.975794,\n", + " 0.977062,\n", + " 0.9783310000000001,\n", + " 0.979607,\n", + " 0.9808840000000001,\n", + " 0.982159,\n", + " 0.983433,\n", + " 0.984708,\n", + " 0.985986,\n", + " 0.9872650000000001,\n", + " 0.9885499999999999,\n", + " 0.989831,\n", + " 0.991111,\n", + " 0.99239,\n", + " 0.993669,\n", + " 0.994951,\n", + " 0.996236,\n", + " 0.997527,\n", + " 0.99882,\n", + " 1.00011,\n", + " 1.0014,\n", + " 1.00269,\n", + " 1.00398,\n", + " 1.00527,\n", + " 1.00657,\n", + " 1.00787,\n", + " 1.00917,\n", + " 1.01047,\n", + " 1.01177,\n", + " 1.0130700000000001,\n", + " 1.01437,\n", + " 1.01567,\n", + " 1.0169700000000002,\n", + " 1.01828,\n", + " 1.01959,\n", + " 1.02089,\n", + " 1.0221900000000002,\n", + " 1.0235,\n", + " 1.0248,\n", + " 1.0261099999999999,\n", + " 1.02742,\n", + " 1.0287300000000001,\n", + " 1.03004,\n", + " 1.0313599999999998,\n", + " 1.0326700000000002,\n", + " 1.0339800000000001,\n", + " 1.03529,\n", + " 1.03661,\n", + " 1.03793,\n", + " 1.03924,\n", + " 1.04055,\n", + " 1.0418699999999999,\n", + " 1.04318,\n", + " 1.0445,\n", + " 1.04581,\n", + " 1.0471300000000001,\n", + " 1.04845,\n", + " 1.04977,\n", + " 1.0510899999999999,\n", + " 1.05241,\n", + " 1.05372,\n", + " 1.05504,\n", + " 1.0563699999999998,\n", + " 1.0577,\n", + " 1.05902,\n", + " 1.06035,\n", + " 1.0616700000000001,\n", + " 1.06298,\n", + " 1.0643,\n", + " 1.06562,\n", + " 1.06695,\n", + " 1.06828,\n", + " 1.06928,\n", + " 1.07028,\n", + " 1.07128,\n", + " 1.07229,\n", + " 1.07329,\n", + " 1.07462,\n", + " 1.07562,\n", + " 1.0766300000000002,\n", + " 1.07763,\n", + " 1.0786300000000002,\n", + " 1.0796400000000002,\n", + " 1.08064,\n", + " 1.0816400000000002,\n", + " 1.0826500000000001,\n", + " 1.0836500000000002,\n", + " 1.0846600000000002,\n", + " 1.08566,\n", + " 1.08667,\n", + " 1.0876700000000001,\n", + " 1.08868,\n", + " 1.08969,\n", + " 1.0907,\n", + " 1.09172,\n", + " 1.09273,\n", + " 1.09374,\n", + " 1.09476,\n", + " 1.09577,\n", + " 1.09678,\n", + " 1.0977999999999999,\n", + " 1.09882,\n", + " 1.09985,\n", + " 1.10087,\n", + " 1.10189,\n", + " 1.10291,\n", + " 1.10393,\n", + " 1.10494,\n", + " 1.10596,\n", + " 1.10698,\n", + " 1.108,\n", + " 1.10901,\n", + " 1.11003,\n", + " 1.1110499999999999,\n", + " 1.11207,\n", + " 1.1130799999999998,\n", + " 1.11409,\n", + " 1.1151099999999998,\n", + " 1.11612,\n", + " 1.11714,\n", + " 1.11816,\n", + " 1.11917,\n", + " 1.12019,\n", + " 1.1212,\n", + " 1.1222100000000002,\n", + " 1.12321,\n", + " 1.12422,\n", + " 1.12523,\n", + " 1.1262400000000001,\n", + " 1.12726,\n", + " 1.12828,\n", + " 1.1293,\n", + " 1.13032,\n", + " 1.13133,\n", + " 1.13235,\n", + " 1.13337,\n", + " 1.1343900000000002,\n", + " 1.13541,\n", + " 1.13643,\n", + " 1.13745,\n", + " 1.13846,\n", + " 1.13948,\n", + " 1.1405,\n", + " 1.14152,\n", + " 1.14254,\n", + " 1.14356,\n", + " 1.14458,\n", + " 1.1456,\n", + " 1.14663,\n", + " 1.1476600000000001,\n", + " 1.14869,\n", + " 1.14972,\n", + " 1.15074,\n", + " 1.1517600000000001,\n", + " 1.15279,\n", + " 1.15382,\n", + " 1.15485,\n", + " 1.1558800000000002,\n", + " 1.15691,\n", + " 1.1579300000000001,\n", + " 1.15896,\n", + " 1.15999,\n", + " 1.16102,\n", + " 1.16205,\n", + " 1.16308,\n", + " 1.1641,\n", + " 1.1651300000000002,\n", + " 1.16616,\n", + " 1.16718,\n", + " 1.1682000000000001,\n", + " 1.1692200000000001,\n", + " 1.17025,\n", + " 1.17127,\n", + " 1.17231,\n", + " 1.17334,\n", + " 1.1743800000000002,\n", + " 1.17541,\n", + " 1.17645,\n", + " 1.17748,\n", + " 1.17851,\n", + " 1.1795499999999999,\n", + " 1.18059,\n", + " 1.18162,\n", + " 1.18266,\n", + " 1.1837,\n", + " 1.1847400000000001,\n", + " 1.18578,\n", + " 1.18682,\n", + " 1.18787,\n", + " ...]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ From 4e1a579c93fbddf7a67144a45ed6aa94f1e540b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 10 Apr 2024 14:33:50 +0200 Subject: [PATCH 012/114] fix pre-commit hooks --- dsms/knowledge/semantics/units/sparql.py | 1 + examples/convert.py | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 examples/convert.py diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py index 6c70b27..16be8df 100644 --- a/dsms/knowledge/semantics/units/sparql.py +++ b/dsms/knowledge/semantics/units/sparql.py @@ -10,6 +10,7 @@ class UnitSparqlQuery(BaseUnitSparqlQuery): Define a sparql query for fetching the units of an HDf5 column of a KItem """ + # OVERRIDE @property def query(cls) -> str: """Construct sparql query for getting unit for hdf5 column""" diff --git a/examples/convert.py b/examples/convert.py deleted file mode 100644 index 8848281..0000000 --- a/examples/convert.py +++ /dev/null @@ -1,11 +0,0 @@ -from dsms import DSMS, KItem - -dsms = DSMS(host_url="https://stahldigital.materials-data.space", env="../.env") - -uuid = "8d6b034e-efb4-427a-a0d3-666e62762cba" -item = dsms[uuid] - - -item.hdf5.get("Standardkraft").convert_to("kN") - -item.custom_properties.content.Projektnummer.convert_to("kPa") From b10bdfc0c47635617ee4bb02fa0dd9c9cf1bf142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 10 Apr 2024 14:35:47 +0200 Subject: [PATCH 013/114] fix knowledge-select --- dsms/knowledge/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 579aa83..3bbb132 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -56,8 +56,9 @@ def _parse_model(value: Optional[Dict[str, Any]]) -> BaseModel: ) elif dtype == "knowledge-select": warnings.warn( - "knowledge-select not supported for KTypes yet." + "knowledge-select not fully supported for KTypes yet." ) + dtype = str fields[slug] = (dtype, default or None) if fields: config = ConfigDict(extra="allow", arbitrary_types_allowed=True) From 9f3e297129db6271c1d5a82796093106c33d5ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 10 Apr 2024 18:54:19 +0200 Subject: [PATCH 014/114] update custom properties - remove unneeded in between --- dsms/knowledge/kitem.py | 61 ++++---- dsms/knowledge/ktype.py | 4 +- dsms/knowledge/properties/__init__.py | 5 +- .../knowledge/properties/custom_properties.py | 136 ------------------ dsms/knowledge/utils.py | 89 ++++++++++-- 5 files changed, 113 insertions(+), 182 deletions(-) delete mode 100644 dsms/knowledge/properties/custom_properties.py diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index ef5fa1b..3e9ba74 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -15,6 +15,7 @@ Field, ValidationInfo, field_validator, + model_validator, ) @@ -31,7 +32,6 @@ AuthorsProperty, ContactInfo, ContactsProperty, - CustomProperties, ExternalLink, ExternalLinksProperty, KProperty, @@ -51,6 +51,7 @@ _slug_is_available, _slugify, _inspect_hdf5, + _get_ktype_from_id, ) from dsms.knowledge.sparql_interface.utils import _get_subgraph # isort:skip @@ -145,7 +146,7 @@ def __init__(self, **kwargs: "Any") -> None: if not self.dsms: self.dsms = DSMS() - # initalize the kitem + # initialize the kitem super().__init__(**kwargs) # add kitem to buffer @@ -298,21 +299,6 @@ def validate_user_groups( """Validate user groups Field""" return UserGroupsProperty(value) - @field_validator("custom_properties") - @classmethod - def validate_custom_properties(cls, value: Any) -> CustomProperties: - """Validate custom properties Field""" - if isinstance(value, dict) and "content" in value: - value = CustomProperties(content=value["content"]) - elif isinstance(value, dict): - value = CustomProperties(content=value) - elif not isinstance(value, (CustomProperties, dict, type(None))): - raise TypeError( - f"""`custom_properties` must be of type {CustomProperties} or {dict}, - not {type(value)}.""" - ) - return value - @field_validator("created_at") @classmethod def validate_created(cls, value: str) -> Any: @@ -341,20 +327,10 @@ def validate_updated(cls, value: str) -> Any: @classmethod def validate_ktype(cls, value: KType, info: ValidationInfo) -> KType: """Validate the data attribute of the KItem""" - from dsms import Context - - ktype_id = info.data.get("ktype_id") if not value: - if not isinstance(ktype_id, str): - value = Context.ktypes.get(ktype_id.value) - else: - value = Context.ktypes.get(ktype_id) - - if not value: - raise TypeError( - f"KType for `ktype_id={ktype_id}` does not exist." - ) + ktype_id = info.data.get("ktype_id") + value = _get_ktype_from_id(ktype_id) return value @@ -416,15 +392,34 @@ def validate_hdf5( hdf5 = None return hdf5 + @model_validator(mode="after") + @classmethod + def validate_custom_properties(cls, self) -> "KItem": + """Validate the custom properties with respect to the KType of the KItem""" + if not isinstance( + self.custom_properties, (BaseModel, dict, type(None)) + ): + raise TypeError( + f"""Custom properties must be one of the following types: + {(BaseModel, dict, type(None))}. Not {type(self.custom_properties)}""" + ) + # validate content with webform model + if self.ktype.webform and isinstance(self.custom_properties, dict): + content = ( + self.custom_properties.get("content") or self.custom_properties + ) + self.custom_properties = self.ktype.webform(**content) + # set kitem id for custom properties + if isinstance(self.custom_properties, BaseModel): + self.custom_properties.kitem_id = self.id + return self + def _set_kitem_for_properties(self) -> None: """Set kitem for CustomProperties and KProperties in order to remain the context for the buffer if any of these properties is changed. """ for prop in self.__dict__.values(): - if ( - isinstance(prop, (KProperty, CustomProperties, Summary)) - and not prop.kitem - ): + if isinstance(prop, (KProperty, Summary)) and not prop.kitem: prop.kitem = self @property diff --git a/dsms/knowledge/ktype.py b/dsms/knowledge/ktype.py index 62f8cbd..25ac875 100644 --- a/dsms/knowledge/ktype.py +++ b/dsms/knowledge/ktype.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field, field_validator, model_serializer -from dsms.knowledge.utils import _parse_model +from dsms.knowledge.utils import _create_custom_properties_model class KType(BaseModel): @@ -24,7 +24,7 @@ class KType(BaseModel): @classmethod def create_model(cls, value: Optional[Dict[str, Any]]) -> Dict[str, Any]: """Validate the data schema of the ktype""" - return _parse_model(value) + return _create_custom_properties_model(value) @model_serializer def serialize(self): diff --git a/dsms/knowledge/properties/__init__.py b/dsms/knowledge/properties/__init__.py index 4d4cd10..652465f 100644 --- a/dsms/knowledge/properties/__init__.py +++ b/dsms/knowledge/properties/__init__.py @@ -12,7 +12,7 @@ from dsms.knowledge.properties.authors import Author, AuthorsProperty from dsms.knowledge.properties.base import KProperty, KPropertyItem from dsms.knowledge.properties.contacts import ContactInfo, ContactsProperty -from dsms.knowledge.properties.custom_properties import CustomProperties +from dsms.knowledge.properties.custom_datatype import NumericalDataType from dsms.knowledge.properties.hdf5 import Column, HDF5Container from dsms.knowledge.properties.summary import Summary from dsms.knowledge.properties.user_groups import UserGroup, UserGroupsProperty @@ -33,7 +33,6 @@ ExternalLinksProperty, ) - __all__ = [ "Annotation", "AnnotationsProperty", @@ -41,7 +40,6 @@ "AttachmentsProperty", "Author", "AuthorsProperty", - "CustomProperties", "LinkedKItem", "LinkedKItemsProperty", "ContactInfo", @@ -59,4 +57,5 @@ "KPropertyItem", "HDF5Container", "Column", + "NumericalDataType", ] diff --git a/dsms/knowledge/properties/custom_properties.py b/dsms/knowledge/properties/custom_properties.py deleted file mode 100644 index ddc63b2..0000000 --- a/dsms/knowledge/properties/custom_properties.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Custom properties of a KItem""" - - -from typing import TYPE_CHECKING, Any, Optional -from uuid import UUID - -from pydantic import BaseModel, ConfigDict, Field, model_validator - -from .custom_datatype.numerical import NumericalDataType - -if TYPE_CHECKING: - from typing import Set, Union - - from dsms import Context - - -class CustomProperties(BaseModel): - """Model for the custom properties of the KItem""" - - id: Optional[UUID] = Field(None, description="ID of the KItem") - content: Any = Field({}, description="Constent of the custom kitem") - kitem: Optional[Any] = Field( - None, description="KItem related to the CustomProperties", exclude=True - ) - - model_config = ConfigDict( - extra="forbid", exclude={"kitem", "id"}, validate_assignment=True - ) - - def __str__(self) -> str: - """Pretty print the custom properties""" - fields = ", ".join( - [ - f"{key}={value}" - for key, value in self.__dict__.items() - if key not in self.exclude - ] - ) - return f"{self.__class__.__name__}({fields})" - - def __repr__(self) -> str: - """Pretty print the custom properties""" - return str(self) - - def __hash__(self) -> int: - return hash(str(self)) - - def add(self, content: "Union[dict, Any]") -> None: - """Add data for custom properties""" - if isinstance(content, type(None)): - self.content = {} - elif self.data_schema and isinstance(content, dict): - self.content = self.data_schema(content) - elif not self.data_schema and isinstance(content, dict): - self.content = content - elif self.data_schema: - if isinstance(content, self.data_schema): - self.content = content - else: - raise TypeError( - f"""Item `{content}` must be of type {self.data_schema} or {dict}, - not `{type(content)}`.""" - ) - else: - raise TypeError( - f"""Item `{content}` must be of type {self.data_schema} or {dict}, - not `{type(content)}`.""" - ) - self._mark_as_updated() - - def _mark_as_updated(self) -> None: - if self.kitem and self.id not in self.context.buffers.updated: - self.context.buffers.updated.update({self.id: self.kitem}) - - def delete(self) -> None: - """Delete data for custom properties""" - self.content = None - self._mark_as_updated() - - def update(self, content: "Union[dict, Any]") -> None: - """Update data for custom properties""" - self.content = None - self.add(content) - - def get(self) -> Any: - """Get data for custom properties""" - return self.content - - @model_validator(mode="after") - @classmethod - def validate_content(cls, self) -> "CustomProperties": - """Validate the custom properties with respect to the KType of the KItem""" - if self.kitem and isinstance(self.content, dict): - # validate content with webform model - if self.kitem.ktype.webform: - self.content = self.kitem.ktype.webform(**self.content) - # set name and property of the inidivdual properties - if isinstance(self.content, dict): - iterable = self.content - else: - iterable = self.content.dict() - for name, subproperty in iterable.items(): - if isinstance(subproperty, NumericalDataType): - if not subproperty.name: - subproperty.name = name - if not subproperty.kitem_id: - subproperty.kitem_id = self.kitem.id - return self - - @property - def id(cls) -> Optional[UUID]: - """Identifier of the KItem related to the CustomProperies""" - if not cls.kitem: - raise ValueError("KItem not defined yet.") - return cls.kitem.id # pylint: disable=E1101 - - @property - def data_schema(cls): - """Data schema related to the ktype of the associated kitem.""" - if not cls.kitem: - raise ValueError("KItem not defined yet.") - return cls.kitem.ktype.webform # pylint: disable=E1101 - - @property - def context(cls) -> "Context": - """Getter for Context""" - from dsms import ( # isort:skip - Context, - ) - - return Context - - @property - def exclude(cls) -> "Optional[Set[str]]": - """Fields to be excluded from the JSON-schema""" - return cls.model_config.get("exclude") diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 3bbb132..a213e19 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -9,11 +9,21 @@ from uuid import UUID import pandas as pd -from pydantic import BaseModel, ConfigDict, create_model, model_validator from requests import Response -from dsms.core.utils import _name_to_camel, _perform_request -from dsms.knowledge.properties.custom_datatype import NumericalDataType +from pydantic import ( # isort: skip + BaseModel, + ConfigDict, + Field, + create_model, + model_validator, +) + +from dsms.core.utils import _name_to_camel, _perform_request # isort:skip + +from dsms.knowledge.properties.custom_datatype import ( # isort:skip + NumericalDataType, +) if TYPE_CHECKING: from dsms.core.context import Buffers @@ -28,7 +38,9 @@ def _is_number(value): return False -def _parse_model(value: Optional[Dict[str, Any]]) -> BaseModel: +def _create_custom_properties_model( + value: Optional[Dict[str, Any]] +) -> BaseModel: """Convert the dict with the model schema into a pydantic model.""" fields = {} @@ -61,25 +73,82 @@ def _parse_model(value: Optional[Dict[str, Any]]) -> BaseModel: dtype = str fields[slug] = (dtype, default or None) if fields: - config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + fields["kitem_id"] = ( + Optional[Union[str, UUID]], + Field(None, exclude=True), + ) + + config = ConfigDict( + extra="allow", arbitrary_types_allowed=True, exclude={"kitem_id"} + ) validators = { "validate_model": model_validator(mode="before")(_validate_model) } model = create_model( title, __config__=config, __validators__=validators, **fields ) + setattr(model, "__str__", _print_properties) + setattr(model, "__repr__", _print_properties) + setattr(model, "__setattr__", __setattr_property__) else: model = None return model -def _validate_model(cls, values: dict): # pylint: disable=unused-argument +def _print_properties(self: Any) -> str: + fields = ", \n".join( + [ + f"\t\t{key}={value}" + for key, value in self.model_dump().items() + if key not in self.model_config["exclude"] + ] + ) + return f"{{\n{fields}\n\t\t}}" + + +def __setattr_property__(self, key, value) -> None: + if _is_number(value): + value = _create_numerical_dtype(key, value, self.kitem_id) + if key == "kitem_id": + for prop in self.model_dump().values(): + if isinstance(prop, NumericalDataType) and not prop.kitem_id: + prop.kitem_id = value + super(BaseModel, self).__setattr__(key, value) + + +def _create_numerical_dtype( + key: str, value: Union[int, float], kitem_id: Union[str, UUID] +) -> NumericalDataType: + value = NumericalDataType(value) + value.name = key + value.kitem_id = kitem_id + return value + + +def _validate_model( + cls, values: Dict[str, Any] # pylint: disable=unused-argument +) -> Dict[str, Any]: for key, value in values.items(): if _is_number(value): - values[key] = NumericalDataType(value) + values[key] = _create_numerical_dtype( + key, value, values.get("kitem_id") + ) return values +def _get_ktype_from_id(ktype_id: str) -> "KType": + from dsms import Context + + if not isinstance(ktype_id, str): + value = Context.ktypes.get(ktype_id.value) + else: + value = Context.ktypes.get(ktype_id) + + if not value: + raise TypeError(f"KType for `ktype_id={ktype_id}` does not exist.") + return value + + def _get_remote_ktypes() -> Enum: """Get the KTypes from the remote backend""" from dsms import ( # isort:skip @@ -166,6 +235,7 @@ def _update_kitem(kitem: "KItem") -> Response: exclude={ "authors", "annotations", + "custom_properties", "linked_kitems", "updated_at", "avatar_exists", @@ -180,8 +250,11 @@ def _update_kitem(kitem: "KItem") -> Response: }, exclude_none=True, ) + custom_properties = kitem.custom_properties.model_dump_json() payload = json.loads(dumped) - payload.update(**differences) + payload.update( + custom_properties={"content": custom_properties}, **differences + ) payload.update( external_links={ link.label: str(link.url) for link in kitem.external_links From 88d5ba68c8571cbeea44498f173738da326610d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 11 Apr 2024 12:35:41 +0200 Subject: [PATCH 015/114] assure that custom properties are updated --- dsms/knowledge/kitem.py | 57 +++++++++++++++-- dsms/knowledge/ktype.py | 4 +- .../properties/custom_datatype/numerical.py | 19 +++--- dsms/knowledge/utils.py | 63 +++++++++++-------- tests/conftest.py | 5 -- tests/test_kitem.py | 31 +++++++++ 6 files changed, 132 insertions(+), 47 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 3e9ba74..0de85af 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -1,5 +1,6 @@ """Knowledge Item implementation of the DSMS""" +import json from datetime import datetime from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union @@ -62,12 +63,51 @@ class KItem(BaseModel): - """Knowledge Item of the DSMS.""" + """ + Knowledge Item of the DSMS. + + Attributes: + name (str): + Human readable name of the KContext.dsms. + id (Optional[UUID]): + ID of the KItem. Defaults to a new UUID if not provided. + ktype_id (Union[Enum, str]): + Type ID of the KItem. + slug (Optional[str]): + Slug of the KContext.dsms. Minimum length: 4. + annotations (List[Annotation]): + Annotations of the KItem. + attachments (List[Union[Attachment, str]]): + File attachments of the DSMS. + linked_kitems (List[Union[LinkedKItem, "KItem"]]): + KItems linked to the current KItem. + affiliations (List[Affiliation]): + Affiliations related to a KItem. + authors (List[Union[Author, str]]): + Authorship of the KItem. + avatar_exists (Optional[bool]): + Whether the KItem holds an avatar or not. + contacts (List[ContactInfo]): + Contact information related to the KItem. + created_at (Optional[Union[str, datetime]]): + Time and date when the KItem was created. + updated_at (Optional[Union[str, datetime]]): + Time and date when the KItem was updated. + external_links (List[ExternalLink]): + External links related to the KItem. + kitem_apps (List[App]): Apps related to the KItem. + summary (Optional[Union[str, Summary]]): + Human readable summary text of the KItem. + user_groups (List[UserGroup]): + User groups able to access the KItem. + custom_properties (Optional[Any]): + Custom properties associated with the KItem. + hdf5 (Optional[Union[List[Column], pd.DataFrame, Dict[str, Union[List, Dict]]]]): + HDF5 interface. + """ # public - name: str = Field( - ..., description="Human readable name of the KContext.dsms" - ) + name: str = Field(..., description="Human readable name of the KItem") id: Optional[UUID] = Field( default_factory=uuid4, description="ID of the KItem", @@ -408,10 +448,17 @@ def validate_custom_properties(cls, self) -> "KItem": content = ( self.custom_properties.get("content") or self.custom_properties ) + if isinstance(content, str): + try: + content = json.loads(content) + except Exception as error: + raise TypeError( + f"Invalid type: {type(content)}" + ) from error self.custom_properties = self.ktype.webform(**content) # set kitem id for custom properties if isinstance(self.custom_properties, BaseModel): - self.custom_properties.kitem_id = self.id + self.custom_properties.kitem = self return self def _set_kitem_for_properties(self) -> None: diff --git a/dsms/knowledge/ktype.py b/dsms/knowledge/ktype.py index 25ac875..d695541 100644 --- a/dsms/knowledge/ktype.py +++ b/dsms/knowledge/ktype.py @@ -22,8 +22,8 @@ class KType(BaseModel): @field_validator("webform") @classmethod - def create_model(cls, value: Optional[Dict[str, Any]]) -> Dict[str, Any]: - """Validate the data schema of the ktype""" + def create_model(cls, value: Optional[Dict[str, Any]]) -> Any: + """Create the datamodel for the ktype""" return _create_custom_properties_model(value) @model_serializer diff --git a/dsms/knowledge/properties/custom_datatype/numerical.py b/dsms/knowledge/properties/custom_datatype/numerical.py index 16d13cb..f4e4754 100644 --- a/dsms/knowledge/properties/custom_datatype/numerical.py +++ b/dsms/knowledge/properties/custom_datatype/numerical.py @@ -8,26 +8,27 @@ ) if TYPE_CHECKING: - from typing import Any, Dict, Optional, Union - from uuid import UUID + from typing import Any, Dict, Optional + + from dsms import KItem class NumericalDataType(float): """Custom Base data type for custom properties""" def __init__(self, value) -> None: # pylint: disable=unused-argument - self._kitem_id: "Optional[Union[str, UUID]]" = None + self._kitem: "Optional[KItem]" = None self._name: "Optional[str]" = None @property - def kitem_id(cls) -> "Optional[Union[str, UUID]]": + def kitem(cls) -> "Optional[KItem]": """Context of the current kitem for this property""" - return cls._kitem_id + return cls._kitem - @kitem_id.setter - def kitem_id(cls, value: "Optional[Union[str, UUID]]") -> None: + @kitem.setter + def kitem(cls, value: "Optional[KItem]") -> None: """Setter for current KItem context""" - cls._kitem_id = value + cls._kitem = value @property def name(cls) -> str: @@ -41,7 +42,7 @@ def name(cls, value: str) -> None: def get_unit(self) -> "Dict[str, Any]": """Get unit for the property""" - return get_property_unit(self.kitem_id, self.name) + return get_property_unit(self.kitem.id, self.name) def convert_to( self, diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index a213e19..5dc29e6 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -42,6 +42,7 @@ def _create_custom_properties_model( value: Optional[Dict[str, Any]] ) -> BaseModel: """Convert the dict with the model schema into a pydantic model.""" + from dsms import KItem fields = {} if isinstance(value, dict): @@ -72,26 +73,25 @@ def _create_custom_properties_model( ) dtype = str fields[slug] = (dtype, default or None) - if fields: - fields["kitem_id"] = ( - Optional[Union[str, UUID]], - Field(None, exclude=True), - ) - - config = ConfigDict( - extra="allow", arbitrary_types_allowed=True, exclude={"kitem_id"} - ) - validators = { - "validate_model": model_validator(mode="before")(_validate_model) - } - model = create_model( - title, __config__=config, __validators__=validators, **fields - ) - setattr(model, "__str__", _print_properties) - setattr(model, "__repr__", _print_properties) - setattr(model, "__setattr__", __setattr_property__) else: - model = None + title = "CustomPropertiesModel" + fields["kitem"] = ( + Optional[KItem], + Field(None, exclude=True), + ) + + config = ConfigDict( + extra="allow", arbitrary_types_allowed=True, exclude={"kitem"} + ) + validators = { + "validate_model": model_validator(mode="before")(_validate_model) + } + model = create_model( + title, __config__=config, __validators__=validators, **fields + ) + setattr(model, "__str__", _print_properties) + setattr(model, "__repr__", _print_properties) + setattr(model, "__setattr__", __setattr_property__) return model @@ -108,20 +108,31 @@ def _print_properties(self: Any) -> str: def __setattr_property__(self, key, value) -> None: if _is_number(value): - value = _create_numerical_dtype(key, value, self.kitem_id) - if key == "kitem_id": + # convert to convertable numeric object + value = _create_numerical_dtype(key, value, self.kitem) + # mark as updated + if self.kitem: + self.kitem.context.buffers.updated.update( + {self.kitem.id: self.kitem} + ) + if key == "kitem": + # set kitem for convertable numeric datatype for prop in self.model_dump().values(): - if isinstance(prop, NumericalDataType) and not prop.kitem_id: - prop.kitem_id = value + if isinstance(prop, NumericalDataType) and not prop.kitem: + prop.kitem = value + # reassignment of extra fields does not work, seems to be a bug? + # have this workaround: + if key in self.__pydantic_extra__: + self.__pydantic_extra__[key] = value super(BaseModel, self).__setattr__(key, value) def _create_numerical_dtype( - key: str, value: Union[int, float], kitem_id: Union[str, UUID] + key: str, value: Union[int, float], kitem: "KItem" ) -> NumericalDataType: value = NumericalDataType(value) value.name = key - value.kitem_id = kitem_id + value.kitem = kitem return value @@ -131,7 +142,7 @@ def _validate_model( for key, value in values.items(): if _is_number(value): values[key] = _create_numerical_dtype( - key, value, values.get("kitem_id") + key, value, values.get("kitem") ) return values diff --git a/tests/conftest.py b/tests/conftest.py index fe4b533..cb920ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,11 +85,6 @@ def return_ktypes(request): { "name": "organization", "id": "organization", - "data_schema": { - "title": "Organization", - "type": "object", - "properties": {}, - }, }, ] header = {"content_type": "application/json"} diff --git a/tests/test_kitem.py b/tests/test_kitem.py index eea192a..6eaf7de 100644 --- a/tests/test_kitem.py +++ b/tests/test_kitem.py @@ -159,3 +159,34 @@ def test_ktype_property(get_mock_kitem_ids, custom_address): ) assert kitem.ktype == Context.ktypes.get(dsms.ktypes.Organization.value) + + +# @responses.activate +# def test_ktype_custom_property_assignment(get_mock_kitem_ids, custom_address): +# from dsms.knowledge.properties.custom_datatype.numerical import NumericalDataType +# from dsms.core.dsms import DSMS +# from dsms.knowledge.kitem import KItem + +# with pytest.warns(UserWarning, match="No authentication details"): +# dsms = DSMS(host_url=custom_address) + +# kitem = KItem( +# id=get_mock_kitem_ids[0], +# name="foo123", +# ktype_id=dsms.ktypes.Organization, +# custom_properties={"material": "abcd", "tester": 123} +# ) + +# assert kitem.custom_properties.material == "abcd" +# assert kitem.custom_properties.tester == 123 +# assert isinstance(kitem.custom_properties.tester, NumericalDataType) + +# kitem.custom_properties = {"material": "def", "tester2": 123} + +# assert kitem.custom_properties.material == "def" +# assert kitem.custom_properties.tester2 == 123 +# assert isinstance(kitem.custom_properties.tester2, NumericalDataType) + +# kitem.custom_properties.tester2 = 456 +# assert kitem.custom_properties.tester2 == 456 +# assert isinstance(kitem.custom_properties.tester2, NumericalDataType) From 46f800a3182d460c717056382412ed3c4936b176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 11 Apr 2024 15:08:02 +0200 Subject: [PATCH 016/114] update basic usage notebook --- examples/basic_usage.ipynb | 951 +++++++++---------------------------- 1 file changed, 228 insertions(+), 723 deletions(-) diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 53575f5..5641041 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -25,7 +25,6 @@ "import os\n", "from pprint import pprint\n", "\n", - "from dotenv import load_dotenv\n", "\n", "from dsms import DSMS, KItem" ] @@ -43,13 +42,7 @@ "metadata": {}, "outputs": [], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" ] }, { @@ -74,7 +67,90 @@ { "data": { "text/plain": [ - "[]" + "[KItem(\n", + " \n", + " \tname = YS2-Fz-D3Q, \n", + " \n", + " \tid = 367a77d1-1ebd-425c-b9a1-bd422e55fc92, \n", + " \n", + " \tktype_id = dataset, \n", + " \n", + " \tslug = ys2fzd3q, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-03-25 09:58:21.246762, \n", + " \n", + " \tupdated_at = 2024-03-25 09:58:21.246762, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None\n", + " ),\n", + " KItem(\n", + " \n", + " \tname = YS2-Fz-D3L, \n", + " \n", + " \tid = 05ece5dc-8062-4f8e-8d01-939670b1ea11, \n", + " \n", + " \tktype_id = dataset, \n", + " \n", + " \tslug = ys2fzd3l, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-03-25 09:57:24.632875, \n", + " \n", + " \tupdated_at = 2024-03-25 09:57:24.632875, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None\n", + " )]" ] }, "execution_count": 3, @@ -95,636 +171,6 @@ "The schema of the KItem itself is a JSON schema which is machine-readable and can be directly incorporated into [Swagger](https://swagger.io/tools/swagger-ui/)-supported APIs like e.g. [`FastAPI`](https://fastapi.tiangolo.com/)." ] }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'$defs': {'AdditionalProperties': {'description': 'Additional properties of',\n", - " 'properties': {'triggerUponUpload': {'default': False,\n", - " 'description': 'Whether '\n", - " 'the '\n", - " 'app '\n", - " 'should '\n", - " 'be '\n", - " 'triggered '\n", - " 'when '\n", - " 'a '\n", - " 'file '\n", - " 'is '\n", - " 'uploaded',\n", - " 'title': 'Triggeruponupload',\n", - " 'type': 'boolean'},\n", - " 'triggerUponUploadFileExtensions': {'anyOf': [{'items': {'type': 'string'},\n", - " 'type': 'array'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'File '\n", - " 'extensions '\n", - " 'for '\n", - " 'which '\n", - " 'the '\n", - " 'upload '\n", - " 'shall '\n", - " 'be '\n", - " 'triggered.',\n", - " 'title': 'Triggeruponuploadfileextensions'}},\n", - " 'title': 'AdditionalProperties',\n", - " 'type': 'object'},\n", - " 'Affiliation': {'description': 'Affiliation of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'affiliation',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name'],\n", - " 'title': 'Affiliation',\n", - " 'type': 'object'},\n", - " 'Annotation': {'description': 'KItem annotation model',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'iri': {'description': 'IRI of the '\n", - " 'annotation',\n", - " 'title': 'Iri',\n", - " 'type': 'string'},\n", - " 'name': {'description': 'Name of the '\n", - " 'annotation',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'namespace': {'description': 'Namespace '\n", - " 'of the '\n", - " 'annotation',\n", - " 'title': 'Namespace',\n", - " 'type': 'string'}},\n", - " 'required': ['iri', 'name', 'namespace'],\n", - " 'title': 'Annotation',\n", - " 'type': 'object'},\n", - " 'App': {'description': 'App of a KItem.',\n", - " 'properties': {'additionalProperties': {'anyOf': [{'$ref': '#/$defs/AdditionalProperties'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Additional '\n", - " 'properties '\n", - " 'related '\n", - " 'to '\n", - " 'the '\n", - " 'appilcation'},\n", - " 'description': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Description '\n", - " 'of the '\n", - " 'appilcation',\n", - " 'title': 'Description'},\n", - " 'executable': {'description': 'Name of the '\n", - " 'executable '\n", - " 'related to '\n", - " 'the app',\n", - " 'title': 'Executable',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related to '\n", - " 'the KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'kitemAppId': {'anyOf': [{'type': 'integer'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'ID of the '\n", - " 'KItem App',\n", - " 'title': 'Kitemappid'},\n", - " 'tags': {'anyOf': [{'type': 'object'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Tags related to the '\n", - " 'appilcation',\n", - " 'title': 'Tags'},\n", - " 'title': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Title of the '\n", - " 'appilcation',\n", - " 'title': 'Title'}},\n", - " 'required': ['executable'],\n", - " 'title': 'App',\n", - " 'type': 'object'},\n", - " 'Attachment': {'description': 'Attachment uploaded by a certain '\n", - " 'user.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'File name of '\n", - " 'the '\n", - " 'attachment',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name'],\n", - " 'title': 'Attachment',\n", - " 'type': 'object'},\n", - " 'Author': {'description': 'Author of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related '\n", - " 'to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'userId': {'description': 'ID of the DSMS '\n", - " 'User',\n", - " 'format': 'uuid',\n", - " 'title': 'Userid',\n", - " 'type': 'string'}},\n", - " 'required': ['userId'],\n", - " 'title': 'Author',\n", - " 'type': 'object'},\n", - " 'Column': {'description': 'Column of an HDF5 data frame',\n", - " 'properties': {'columnId': {'description': 'Column ID in '\n", - " 'the data '\n", - " 'frame',\n", - " 'title': 'Columnid',\n", - " 'type': 'integer'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related '\n", - " 'to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'column in the '\n", - " 'data series.',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['columnId', 'name'],\n", - " 'title': 'Column',\n", - " 'type': 'object'},\n", - " 'ContactInfo': {'description': 'Contact info',\n", - " 'properties': {'email': {'description': 'EMail of '\n", - " 'the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Email',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'userId': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'User ID '\n", - " 'of the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Userid'}},\n", - " 'required': ['name', 'email'],\n", - " 'title': 'ContactInfo',\n", - " 'type': 'object'},\n", - " 'ExternalLink': {'description': 'External link of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'label': {'description': 'Label of '\n", - " 'the '\n", - " 'external '\n", - " 'link',\n", - " 'title': 'Label',\n", - " 'type': 'string'},\n", - " 'url': {'description': 'URL of the '\n", - " 'external '\n", - " 'link',\n", - " 'format': 'uri',\n", - " 'minLength': 1,\n", - " 'title': 'Url',\n", - " 'type': 'string'}},\n", - " 'required': ['label', 'url'],\n", - " 'title': 'ExternalLink',\n", - " 'type': 'object'},\n", - " 'KItem': {'additionalProperties': False,\n", - " 'description': 'Knowledge Item of the DSMS.',\n", - " 'properties': {'affiliations': {'default': [],\n", - " 'description': 'Affiliations '\n", - " 'related '\n", - " 'to a '\n", - " 'KItem.',\n", - " 'items': {'$ref': '#/$defs/Affiliation'},\n", - " 'title': 'Affiliations',\n", - " 'type': 'array'},\n", - " 'annotations': {'default': [],\n", - " 'description': 'Annotations '\n", - " 'of the '\n", - " 'KItem',\n", - " 'items': {'$ref': '#/$defs/Annotation'},\n", - " 'title': 'Annotations',\n", - " 'type': 'array'},\n", - " 'attachments': {'default': [],\n", - " 'description': 'File '\n", - " 'attachements '\n", - " 'of the '\n", - " 'DSMS',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/Attachment'},\n", - " {'type': 'string'}]},\n", - " 'title': 'Attachments',\n", - " 'type': 'array'},\n", - " 'authors': {'default': [],\n", - " 'description': 'Authorship of '\n", - " 'the KItem.',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/Author'},\n", - " {'type': 'string'}]},\n", - " 'title': 'Authors',\n", - " 'type': 'array'},\n", - " 'avatar_exists': {'anyOf': [{'type': 'boolean'},\n", - " {'type': 'null'}],\n", - " 'default': False,\n", - " 'description': 'Whether '\n", - " 'the '\n", - " 'KItem '\n", - " 'holds an '\n", - " 'avatar '\n", - " 'or not.',\n", - " 'title': 'Avatar Exists'},\n", - " 'contacts': {'default': [],\n", - " 'description': 'Whether the '\n", - " 'KItem holds '\n", - " 'any contact '\n", - " 'information.',\n", - " 'items': {'$ref': '#/$defs/ContactInfo'},\n", - " 'title': 'Contacts',\n", - " 'type': 'array'},\n", - " 'created_at': {'anyOf': [{'type': 'string'},\n", - " {'format': 'date-time',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Time and '\n", - " 'date when '\n", - " 'the KItem '\n", - " 'was '\n", - " 'created.',\n", - " 'title': 'Created At'},\n", - " 'custom_properties': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': {},\n", - " 'description': 'Custom '\n", - " 'properties '\n", - " 'associated '\n", - " 'to '\n", - " 'the '\n", - " 'KItem',\n", - " 'title': 'Custom '\n", - " 'Properties'},\n", - " 'external_links': {'default': [],\n", - " 'description': 'External '\n", - " 'links '\n", - " 'related '\n", - " 'to the '\n", - " 'KItem',\n", - " 'items': {'$ref': '#/$defs/ExternalLink'},\n", - " 'title': 'External '\n", - " 'Links',\n", - " 'type': 'array'},\n", - " 'hdf5': {'anyOf': [{'items': {'$ref': '#/$defs/Column'},\n", - " 'type': 'array'},\n", - " {'additionalProperties': {'anyOf': [{'items': {},\n", - " 'type': 'array'},\n", - " {'type': 'object'}]},\n", - " 'type': 'object'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'HDF5 interface.',\n", - " 'title': 'Hdf5'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'description': 'ID of the KItem',\n", - " 'title': 'Id'},\n", - " 'kitem_apps': {'default': [],\n", - " 'description': 'Apps '\n", - " 'related to '\n", - " 'the KItem.',\n", - " 'items': {'$ref': '#/$defs/App'},\n", - " 'title': 'Kitem Apps',\n", - " 'type': 'array'},\n", - " 'ktype': {'anyOf': [{'$ref': '#/$defs/KType'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KType of the '\n", - " 'KItem'},\n", - " 'ktype_id': {'anyOf': [{'description': 'Create '\n", - " 'a '\n", - " 'collection '\n", - " 'of '\n", - " 'name/value '\n", - " 'pairs.\\n'\n", - " '\\n'\n", - " 'Example '\n", - " 'enumeration:\\n'\n", - " '\\n'\n", - " '>>> '\n", - " 'class '\n", - " 'Color(Enum):\\n'\n", - " '... '\n", - " 'RED '\n", - " '= '\n", - " '1\\n'\n", - " '... '\n", - " 'BLUE '\n", - " '= '\n", - " '2\\n'\n", - " '... '\n", - " 'GREEN '\n", - " '= '\n", - " '3\\n'\n", - " '\\n'\n", - " 'Access '\n", - " 'them '\n", - " 'by:\\n'\n", - " '\\n'\n", - " '- '\n", - " 'attribute '\n", - " 'access:\\n'\n", - " '\\n'\n", - " ' '\n", - " '>>> '\n", - " 'Color.RED\\n'\n", - " ' '\n", - " '\\n'\n", - " '\\n'\n", - " '- '\n", - " 'value '\n", - " 'lookup:\\n'\n", - " '\\n'\n", - " ' '\n", - " '>>> '\n", - " 'Color(1)\\n'\n", - " ' '\n", - " '\\n'\n", - " '\\n'\n", - " '- '\n", - " 'name '\n", - " 'lookup:\\n'\n", - " '\\n'\n", - " ' '\n", - " '>>> '\n", - " \"Color['RED']\\n\"\n", - " ' '\n", - " '\\n'\n", - " '\\n'\n", - " 'Enumerations '\n", - " 'can '\n", - " 'be '\n", - " 'iterated '\n", - " 'over, '\n", - " 'and '\n", - " 'know '\n", - " 'how '\n", - " 'many '\n", - " 'members '\n", - " 'they '\n", - " 'have:\\n'\n", - " '\\n'\n", - " '>>> '\n", - " 'len(Color)\\n'\n", - " '3\\n'\n", - " '\\n'\n", - " '>>> '\n", - " 'list(Color)\\n'\n", - " '[, '\n", - " ', '\n", - " ']\\n'\n", - " '\\n'\n", - " 'Methods '\n", - " 'can '\n", - " 'be '\n", - " 'added '\n", - " 'to '\n", - " 'enumerations, '\n", - " 'and '\n", - " 'members '\n", - " 'can '\n", - " 'have '\n", - " 'their '\n", - " 'own\\n'\n", - " 'attributes '\n", - " '-- '\n", - " 'see '\n", - " 'the '\n", - " 'documentation '\n", - " 'for '\n", - " 'details.',\n", - " 'enum': [],\n", - " 'title': 'Enum'},\n", - " {'type': 'string'}],\n", - " 'description': 'Type ID of '\n", - " 'the KItem',\n", - " 'title': 'Ktype Id'},\n", - " 'linked_kitems': {'default': [],\n", - " 'description': 'KItems '\n", - " 'linked '\n", - " 'to the '\n", - " 'current '\n", - " 'KItem.',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/LinkedKItem'},\n", - " {'$ref': '#/$defs/KItem'}]},\n", - " 'title': 'Linked Kitems',\n", - " 'type': 'array'},\n", - " 'name': {'description': 'Human readable '\n", - " 'name of the '\n", - " 'KContext.dsms',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'slug': {'anyOf': [{'minLength': 4,\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Slug of the '\n", - " 'KContext.dsms',\n", - " 'title': 'Slug'},\n", - " 'summary': {'anyOf': [{'type': 'string'},\n", - " {'$ref': '#/$defs/Summary'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Human readable '\n", - " 'summary text '\n", - " 'of the KItem.',\n", - " 'title': 'Summary'},\n", - " 'updated_at': {'anyOf': [{'type': 'string'},\n", - " {'format': 'date-time',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Time and '\n", - " 'date when '\n", - " 'the KItem '\n", - " 'was '\n", - " 'updated.',\n", - " 'title': 'Updated At'},\n", - " 'user_groups': {'default': [],\n", - " 'description': 'User '\n", - " 'groups '\n", - " 'able to '\n", - " 'access the '\n", - " 'KItem.',\n", - " 'items': {'$ref': '#/$defs/UserGroup'},\n", - " 'title': 'User Groups',\n", - " 'type': 'array'}},\n", - " 'required': ['name', 'ktype_id'],\n", - " 'title': 'KItem',\n", - " 'type': 'object'},\n", - " 'KType': {'description': 'Knowledge type of the knowledge item.',\n", - " 'properties': {'data_schema': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'OpenAPI '\n", - " 'schema of '\n", - " 'the KItem.',\n", - " 'title': 'Data Schema'},\n", - " 'form_data': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Form data of '\n", - " 'the KItem.',\n", - " 'title': 'Form Data'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'string'}],\n", - " 'description': 'ID of the KType.',\n", - " 'title': 'Id'},\n", - " 'name': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Human readable '\n", - " 'name of the '\n", - " 'KType.',\n", - " 'title': 'Name'}},\n", - " 'required': ['id'],\n", - " 'title': 'KType',\n", - " 'type': 'object'},\n", - " 'LinkedKItem': {'description': 'Data model of a linked KItem',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'ID of the '\n", - " 'KItem to be '\n", - " 'linked',\n", - " 'title': 'Id'},\n", - " 'sourceId': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Source '\n", - " 'ID of '\n", - " 'the '\n", - " 'KItem',\n", - " 'title': 'Sourceid'}},\n", - " 'title': 'LinkedKItem',\n", - " 'type': 'object'},\n", - " 'Summary': {'additionalProperties': False,\n", - " 'description': 'Model for the custom properties of the '\n", - " 'KItem',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'title': 'Id'},\n", - " 'kitem': {'anyOf': [{}, {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem related '\n", - " 'to the summary',\n", - " 'title': 'Kitem'},\n", - " 'text': {'description': 'Summary text of '\n", - " 'the KItem',\n", - " 'title': 'Text',\n", - " 'type': 'string'}},\n", - " 'required': ['text'],\n", - " 'title': 'Summary',\n", - " 'type': 'object'},\n", - " 'UserGroup': {'description': 'Users groups related to a KItem.',\n", - " 'properties': {'groupId': {'description': 'ID of the '\n", - " 'user group',\n", - " 'title': 'Groupid',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'user group',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name', 'groupId'],\n", - " 'title': 'UserGroup',\n", - " 'type': 'object'}},\n", - " 'allOf': [{'$ref': '#/$defs/KItem'}]}\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Anaconda3\\envs\\sdk\\Lib\\site-packages\\pydantic\\json_schema.py:2099: PydanticJsonSchemaWarning: Default value is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n", - " warnings.warn(message, PydanticJsonSchemaWarning)\n" - ] - } - ], - "source": [ - "pprint(KItem.model_json_schema())" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -734,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -746,7 +192,11 @@ "KTypes.App\n", "KTypes.DatasetCatalog\n", "KTypes.TestSeries\n", - "KTypes.TestMatthias\n" + "KTypes.TestMatthias\n", + "KTypes.Characterizationprocedure\n", + "KTypes.Dataset\n", + "KTypes.Specimen\n", + "KTypes.Formtest2\n" ] } ], @@ -771,7 +221,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -781,7 +231,7 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", + "\tid = 680904c8-d502-41bd-9b0c-96da6891dcce, \n", "\n", "\tktype_id = KTypes.DatasetCatalog, \n", "\n", @@ -813,13 +263,15 @@ "\n", "\tuser_groups = [], \n", "\n", - "\tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", + "\tcustom_properties = {\n", + "\t\tfoo=bar\n", + "\t\t}, \n", "\n", "\thdf5 = None\n", ")" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -843,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -859,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -869,7 +321,7 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", + "\tid = 680904c8-d502-41bd-9b0c-96da6891dcce, \n", "\n", "\tktype_id = dataset-catalog, \n", "\n", @@ -891,9 +343,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-03-06 10:12:39.377020, \n", + "\tcreated_at = 2024-04-11 12:28:26.391526, \n", "\n", - "\tupdated_at = 2024-03-06 10:12:39.377020, \n", + "\tupdated_at = 2024-04-11 12:28:26.391526, \n", "\n", "\texternal_links = [], \n", "\n", @@ -903,13 +355,15 @@ "\n", "\tuser_groups = [], \n", "\n", - "\tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", + "\tcustom_properties = {\n", + "\t\tfoo=bar\n", + "\t\t}, \n", "\n", "\thdf5 = None\n", ")" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -927,7 +381,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -935,9 +389,51 @@ "text/plain": [ "[KItem(\n", " \n", + " \tname = YS2-Fz-D3Q, \n", + " \n", + " \tid = 367a77d1-1ebd-425c-b9a1-bd422e55fc92, \n", + " \n", + " \tktype_id = dataset, \n", + " \n", + " \tslug = ys2fzd3q, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-03-25 09:58:21.246762, \n", + " \n", + " \tupdated_at = 2024-03-25 09:58:21.246762, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None\n", + " ),\n", + " KItem(\n", + " \n", " \tname = foo123, \n", " \n", - " \tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", + " \tid = 680904c8-d502-41bd-9b0c-96da6891dcce, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", @@ -959,9 +455,53 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-06 10:12:39.377020, \n", + " \tcreated_at = 2024-04-11 12:28:26.391526, \n", + " \n", + " \tupdated_at = 2024-04-11 12:28:26.391526, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = {\n", + " \t\tfoo=bar\n", + " \t\t}, \n", + " \n", + " \thdf5 = None\n", + " ),\n", + " KItem(\n", + " \n", + " \tname = YS2-Fz-D3L, \n", + " \n", + " \tid = 05ece5dc-8062-4f8e-8d01-939670b1ea11, \n", + " \n", + " \tktype_id = dataset, \n", + " \n", + " \tslug = ys2fzd3l, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-03-25 09:57:24.632875, \n", " \n", - " \tupdated_at = 2024-03-06 10:12:39.377020, \n", + " \tupdated_at = 2024-03-25 09:57:24.632875, \n", " \n", " \texternal_links = [], \n", " \n", @@ -971,13 +511,13 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", " )]" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -1007,7 +547,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -1015,7 +555,7 @@ "file = os.path.join(\"..\", \"README.md\")\n", "\n", "item.name = \"foobar\"\n", - "item.custom_properties.update({\"foobar\": \"foobar\"})\n", + "item.custom_properties.foobar = \"foobar\"\n", "item.attachments.append({\"name\": file})\n", "item.annotations.append(\n", " {\n", @@ -1041,7 +581,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -1064,55 +604,20 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\t\t\t Downloaded file: Attachment(name=README.md)\n", - "|------------------------------------Beginning of file------------------------------------|\n", - "# DSMS-SDK\n", - "Python SDK core-package for interacting with the Dataspace Management System (DSMS)\n", - "\n", - "\n", - "## Authors\n", - "\n", - "[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "## License\n", - "\n", - "This project is licensed under the BSD 3-Clause. See the LICENSE file for more information.\n", - "\n", - "## Usage\n", - "\n", - "The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities:\n", - "\n", - "- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType)\n", - " - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test\n", - " - Administrating authorship, contact information and supplementary information upon making changes or adding KItems\n", - " - Semantic annotation of KItems\n", - "- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface\n", - "- Linking KItems to other KItems\n", - "- Linking Apps to KItems, triggererd, for example, during a file upload\n", - "- Performing simple file upload and download using attachments to KItems\n", - "- Export of a knowledge (sub) graph as common serializations (.ttl, .json)\n", - "\n", - "For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items.\n", - "\n", - "\n", - "## Disclaimer\n", - "\n", - "Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM.\n", - "\n", - "Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de)\n", - "\n", - "|---------------------------------------End of file---------------------------------------|\n" + "ename": "RuntimeError", + "evalue": "Download for attachment `..\\README.md` was not successful: {\"detail\":\"File not found\"}", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[11], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m file \u001b[38;5;129;01min\u001b[39;00m item\u001b[38;5;241m.\u001b[39mattachments:\n\u001b[1;32m----> 2\u001b[0m download \u001b[38;5;241m=\u001b[39m \u001b[43mfile\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdownload\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;124m Downloaded file:\u001b[39m\u001b[38;5;124m\"\u001b[39m, file)\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m|------------------------------------Beginning of file------------------------------------|\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "File \u001b[1;32mc:\\users\\bue\\dsms-python-sdk\\dsms\\knowledge\\properties\\attachments.py:21\u001b[0m, in \u001b[0;36mAttachment.download\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdownload\u001b[39m(\u001b[38;5;28mself\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mstr\u001b[39m:\n\u001b[0;32m 20\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Download attachment file\"\"\"\u001b[39;00m\n\u001b[1;32m---> 21\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_get_attachment\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mid\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mname\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\users\\bue\\dsms-python-sdk\\dsms\\knowledge\\utils.py:344\u001b[0m, in \u001b[0;36m_get_attachment\u001b[1;34m(kitem_id, file_name)\u001b[0m\n\u001b[0;32m 342\u001b[0m response \u001b[38;5;241m=\u001b[39m _perform_request(url, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mget\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 343\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m response\u001b[38;5;241m.\u001b[39mok:\n\u001b[1;32m--> 344\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\n\u001b[0;32m 345\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDownload for attachment `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfile_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m` was not successful: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse\u001b[38;5;241m.\u001b[39mtext\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 346\u001b[0m )\n\u001b[0;32m 347\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m response\u001b[38;5;241m.\u001b[39mtext\n", + "\u001b[1;31mRuntimeError\u001b[0m: Download for attachment `..\\README.md` was not successful: {\"detail\":\"File not found\"}" ] } ], @@ -1148,7 +653,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -1179,7 +684,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1195,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -1263,7 +768,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1279,7 +784,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1295,7 +800,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1311,7 +816,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -1351,7 +856,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1400,7 +905,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -1947,7 +1452,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2492,7 +1997,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2562,7 +2067,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2680,7 +2185,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2721,7 +2226,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2760,7 +2265,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -2814,7 +2319,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ From 62d9d37da82b344c4ca6db5a420062a8692ada8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 12 Apr 2024 10:32:58 +0200 Subject: [PATCH 017/114] Bump version v1.3.3 -> v1.4.0rc0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index dea3812..60fc999 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.3.3 +version = v1.4.0rc0 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From f490d4ea06a42fb60ae5f2e4fe3515e97466732f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 12 Apr 2024 16:45:44 +0200 Subject: [PATCH 018/114] introduce slug indexing --- dsms/core/configuration.py | 7 +++++++ dsms/knowledge/kitem.py | 15 ++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 4bd8377..b237d79 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -58,6 +58,13 @@ class Configuration(BaseSettings): description="Repository of the triplestore for KItems in the DSMS", ) + individual_slugs: bool = Field( + True, + description="""When set to `True`, the slugs of the KItems will receive the + first few characters of the KItem-id, when the slug is derived automatically + from the KItem-name.""", + ) + @field_validator("token") def validate_auth(cls, val, info: ValidationInfo): """Validate the provided authentication/authorization secrets.""" diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index ef5fa1b..12c3f95 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -362,18 +362,19 @@ def validate_ktype(cls, value: KType, info: ValidationInfo) -> KType: @classmethod def validate_slug(cls, value: str, info: ValidationInfo) -> str: """Validate slug""" + from dsms import Context + ktype_id = info.data["ktype_id"] name = info.data.get("name") kitem_id = info.data.get("id") if not value: value = _slugify(name) - slugified = _slugify(value) - if len(slugified) < 4: - raise ValueError("Slug length must have a minimum length of 4.") - if value != slugified: - raise ValueError( - f"`{value}` is not a valid slug. A valid variation would be `{slugified}`" - ) + if Context.dsms.config.individual_slugs: + value += f"-{str(kitem_id).split('-', maxsplit=1)[0]}" + if len(value) < 4: + raise ValueError( + "Slug length must have a minimum length of 4." + ) if not _kitem_exists(kitem_id) and not _slug_is_available( ktype_id.value, value ): From 808ce9b9991ceecad5c367a5b262a51f72ec5a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 12 Apr 2024 18:00:01 +0200 Subject: [PATCH 019/114] update url property of kitem --- dsms/knowledge/kitem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index d14acd7..43e4b73 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -500,5 +500,5 @@ def context(cls) -> "Context": def url(cls) -> str: """URL of the KItem""" return urljoin( - cls.context.dsms.config.host_url, f"{cls.ktype_id}/{cls.slug}" + str(cls.context.dsms.config.host_url), f"{cls.ktype_id}/{cls.slug}" ) From 2d8a2681b7d91d22e203c3a4057829e02e173f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 16 Apr 2024 08:58:44 +0200 Subject: [PATCH 020/114] update list-typing in dsms-apps --- dsms/knowledge/properties/apps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 333676b..386fba0 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -1,6 +1,6 @@ """App KProperty""" -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, List, Optional from pydantic import BaseModel, Field @@ -17,7 +17,7 @@ class AdditionalProperties(BaseModel): False, description="Whether the app should be triggered when a file is uploaded", ) - triggerUponUploadFileExtensions: Optional[list[str]] = Field( + triggerUponUploadFileExtensions: Optional[List[str]] = Field( None, description="File extensions for which the upload shall be triggered.", ) From 34d4f5ed4d6c6780581ae2a4517468f1ee8ee761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 16 Apr 2024 09:01:02 +0200 Subject: [PATCH 021/114] update python versions tested in github-ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c1dc8e..f4cfb56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: From e6d602be89c4c3fece8a84958917e677554fe2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 16 Apr 2024 09:11:03 +0200 Subject: [PATCH 022/114] update ignored flake8 string errors in python3.12 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 134d167..f111db8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,7 @@ repos: rev: 6.0.0 hooks: - id: flake8 - args: [--count, --show-source, --statistics, '--ignore', 'E501,E203,W503'] + args: [--count, --show-source, --statistics, '--ignore', 'E501,E203,W503,E201,E202,E221,E222,E231,E241,E271,E272,E702,E713'] # additional_dependencies: [flake8-bugbear==21.3.1, pep8-naming] log_file: flake8.log From 1dd6a7218df0488bec4e8859aea7907cdfa04be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 16 Apr 2024 09:16:55 +0200 Subject: [PATCH 023/114] Bump version v1.4.0rc0 -> v1.4.0rc1 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 60fc999..5fff26e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc0 +version = v1.4.0rc1 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From feef705af8fc695ffb29d376e62e25ba4c4fa91d Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Tue, 16 Apr 2024 19:11:33 +0000 Subject: [PATCH 024/114] Adding DSMS Documentation: Intial Structure and First Draft --- docs/source/assets/images/DSMS.jpg | Bin 0 -> 337372 bytes docs/source/assets/images/copy_token_1.jpg | Bin 0 -> 44736 bytes docs/source/assets/images/copy_token_2.jpg | Bin 0 -> 26575 bytes docs/source/assets/images/copy_token_3.jpg | Bin 0 -> 44375 bytes docs/source/assets/images/copy_token_4.jpg | Bin 0 -> 24288 bytes docs/source/assets/images/copy_token_5.jpg | Bin 0 -> 29731 bytes docs/source/assets/images/dsms-sdk.jpg | Bin 0 -> 163424 bytes docs/source/installation.md | 41 ++ docs/source/introduction.md | 48 ++ docs/source/tutorials/1_introduction.ipynb | 743 +++++++++++++++++++++ docs/source/tutorials/2_creation.ipynb | 191 ++++++ docs/source/tutorials/3_updation.ipynb | 171 +++++ docs/source/tutorials/4_deletion.ipynb | 219 ++++++ docs/source/tutorials/5_search.ipynb | 248 +++++++ docs/source/tutorials/6_app_HDF5.ipynb | 182 +++++ 15 files changed, 1843 insertions(+) create mode 100644 docs/source/assets/images/DSMS.jpg create mode 100644 docs/source/assets/images/copy_token_1.jpg create mode 100644 docs/source/assets/images/copy_token_2.jpg create mode 100644 docs/source/assets/images/copy_token_3.jpg create mode 100644 docs/source/assets/images/copy_token_4.jpg create mode 100644 docs/source/assets/images/copy_token_5.jpg create mode 100644 docs/source/assets/images/dsms-sdk.jpg create mode 100644 docs/source/installation.md create mode 100644 docs/source/introduction.md create mode 100644 docs/source/tutorials/1_introduction.ipynb create mode 100644 docs/source/tutorials/2_creation.ipynb create mode 100644 docs/source/tutorials/3_updation.ipynb create mode 100644 docs/source/tutorials/4_deletion.ipynb create mode 100644 docs/source/tutorials/5_search.ipynb create mode 100644 docs/source/tutorials/6_app_HDF5.ipynb diff --git a/docs/source/assets/images/DSMS.jpg b/docs/source/assets/images/DSMS.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1935f82f74b6f04504ceb05a33d0d5fd324a318 GIT binary patch literal 337372 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%0A#eOh6Yv3CJGrCT><=> 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 H;B0Dv~SP6J?rY4~7V8hikn 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�Hws<`j0SRXSIUjscN30`%g51*B*3nq-N`ymC@+S0XJ~qC$RJ6z~i-N7&-!&H9 z7t;F^)GRFKt_XLnC2PO~r`Gi^2Dpb(b$Bu%6Aek`sl7Mi>pk;{-}MLfO^I5&De1%) zv^eJS!4(6`ua`WA-qnE|ZAX#}?3$bE>mmk0LrUhv{!#-*qqJ$Yhe!+fL<{Lr@UO^5sMvHWFdG4mzd|8%8_S=6!r0!m+$u|?16`2C3>Y0F3{;CCyN-+kc)6X-<`)IQn9%dn{;)m z)ZKUUE9Ab7yc>x2jj`e-R;(atVwiVhGbZNJS4L07sHq5)yoZC zb$CD_?N`v;VP(lWlvicSysQuVyHqRZKa?C5@h5Tx;H-DtbIt29!#1*I20GMc&9~Da zRRlji-GOTas3B$j2qt27-RN_z*n)<{17Bk4HHrKZFpV@2H7%CON>cNWnxd93KhvZ!@~ za{2Po1W>SZW#}(zV-eel)6bgRm$9@7r2vgxn+m`%O6@%3mJW#zQAic$6->W;%i=dE z<7Xn|gtmgAJaw|G(!#-#ZHvi)mub05zx`n8HL3oO99s2m2cT`#>cOg!4p1764b^|t zJi62pLXu>h2X`+_L)SKFV;UZ!4cL8Y6$b^#^NstYs6;DgjON%OQ1eJ{*&>~=8}`!QF`xAl-O<|DHJvZ)sQQFR9Cc90EQbSu#Hw}S zqIr{T_2}^rYFxu&pmxfm&o~C&o*qx4eLxHQp2Nl-@ zC}1HXtNR}QyT`kc>5hyUnArI341*U%UCRR;k`n#TitA-!$rVq=X9UReOwEP0DxUT) zTXFPKxP4m4MA_9FZCz{oylsvh&c8W)i3*t)Ciy6_O#_8r$?}_i&Pu)|;8{%Zs%K|5 zCVSHpMmS7Qd71wbMPq3Zwj1AFF#>GyQ5<4NhuCfbY2fx-p(C8_>$D}dsdtUNpnG(4 zuLEm3cfdPSTarVYZK+-T>`pyphx1aS`}eb35nAj#MIu)-*Mw1Unpf=GmmCj(1}CIJ zO)Y4%15`IME&&z#(R@R`LTs*2o`1AMl$WWORfUg!iKapSF`&vP)2|LqOM<(*`mJST zPx9^h<2%x1Qb_EbkG1LjxWDKcKr*O^ijCy80y(rb)51U8Yenxx>2Pq;Ca4g`E(ubR zo@cIRt*_uznfOXT0nWxCd9-vsp!cRxqtB0%AN26p`pB!$8eEp$nMh~*ohPl5zY*s^ z{}W_yZQvQ)`a8wwuP@V=aVlCY92daj+>l}F8_(3mw*XA=7O694A$W7!=Kdv?TU5r? zZ;mL>U*@WTbb>A}izAK-hUl1a0jeOTa2Wv0F?}fR!Ux|kNKEMUJkv=0z)g&4{gbZI z-(4v2zk|oc|AQv~TVe=EzpMw2ZFad=(_F3S*la{gkL4Rdd+G}zagMsmkcA?x8l{&P zG??up9aE<5l>7GG&Y5Dr-m0u8nQmNu;p;Rl%FlEsMPqO&oP%3{X3krB)WE&tjgWA_ z2YI=k?MK@CIrMaUdlpqpTUqkSApUw}D(~9jl&3$-q99vC3q~a4N$XhAO?#}01al<^ zglzKlgLV4>kfoU_57L9f)Ae2CQGEPhx(}_eU*Gc6AEvBR_mRTB3F;n`GtGbvW#T9M z$3*p3Z6MW;uH9g3nRNrtoc%^K+KQ~Vukb-0LSJ6MY4D3(-i1Uo_};Y)vH^ z4YifGh}3l6B=UjhiYX0bdE-xrZD(vK*O205>bcd=k$L86Ll@m literal 0 HcmV?d00001 diff --git a/docs/source/assets/images/copy_token_2.jpg b/docs/source/assets/images/copy_token_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9946b15f2e57a13db258966eeb0af3d4c7a6d244 GIT binary patch literal 26575 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
%%120POvJ z*zN0ZM@0KJwW293{C`r&v+MoDr1+mSD>tpK&R8*0xW5aLLYViu9!D9-_XkhBH8Wy6 z2MEZ%4 zI_4&O;p@#&RZVrw<7eCLHFb+bvPozrTVp@d=fr1nMJR;xJ4d{gTIc`=n#oHeM96Au zJrN%f0mZ-9JriH!;fjKWl%IRZTq&N&UVBc4LSa8!`VBQ)Hm-Ic0(xMmztL&PiZASA z1Qd!2NF(t@VD3)02T%~HUd?H;vSh6WYXr=3@L}N*AZ3koUxan%5&Ij4hI-!5tx(WF zwDAv|kg2TA%j70j$8CCiHa3C+ji$1>6!uGqrbhGhVl{qIG@?csnW@)Mpj}rbLoUZc zB~Ih%?{pL1pb@mgMPi4G2hGz+8^?5V#&m9Jp_#0dLTq|?4Q%T5_=3Z}U46&Tk3uLW zp}!S@^X7?(3<8R$h*MoFAS*f;3yn$YXk;muRcGdW>8?YHU$)?m@aeYqM@VS)@384b zwSOAa<4KD^BMjEmksXywpAibJk+1RxXKiPG_Q`?H9JQzLj@p5&H`7R)f(H#cLo*&eCW-)YkZBkA<%K7u<08e2YclE3Hvf&~D|#^NyNFL#7R=*9Nb z8Cjax0(5VGF)ac={ozYdXnecZjBJW1c(xOM1`U6S!NYx<$!VwJ_g9j{4Yv@BHh1W8 z8VZVm;W=iiWH?<>TO$!bVado648@$gIN;7?AR#vXvU|>saOdkeNNS76dCRnnobCa} zM57Ce6;qubmEDu;v0l?pk9*~KVMN5GKsmYZeSvkvfzHUJ&q@~N=y22sh9*glj({%r z93YL^c!?FQ6)DK5fh-<{)ISPo!df$b4$l(~&oeMw@n}XYjZB3sea3Tc7ml!&=JX|= z62D}TsF4=BSx}T~)r-%U<1fcIkTh!5PWYL)c1c?5c&3kRTDJ+SbM)x0RTBbdukIR- z-2DBddj01O4^M8>eXuDo@POC}`5Hv}%Zj61M-Ija%v=>dEml&9D4V$x!lRqerd(?< z;}ASd-BTPMusdK?m-7BEhVCw|%vH}#n8okPDI)s~c8u`Xe9K+s?CSl{COz_eiqYq~ ziI=}{S0L~1_$IRcs)1FrA6>I+v-eOK4AV>aHkXwEK5Iw zx+XQverv`wwB%D7nrU>Wux~F(OA!z|f4d&$P~^Qn!b?XRAV4g5vY&5^<`0>muok?v z<762$Cjb#^tj7yZWm$kdq#Qn-VqJjZf;(#GSb%HVmzf_WRPuIs3pKu$hu9+baRD+^ zfPySmN=Cs7Y>Aei`Not&Xbkhy`k`Nq*5z}MdIW~O0+JF5CBTwY#j5|jBSJ&8bOyXvP=<2!dJO72>}JQF2W4Ij0gGb z(hu5dxA*ot+l21YJaQ-fq5m0yHzi+21xiIj<&$!*Ct?u~@&;o)*EhRAtntj+v!R@8 z)hE{&P3QOzF*1ezUmeT8i9aGwnP_EJyxl%h#s9|Wa?~4#O*=0hIh@7f3+%|}(fQoj zXD7IoE}p9Q@j(?lIbEcGIQPBZ^sZl-U7mXf=hwOa%sh8Ad&kpZ%BfkW!gU9NJA&S8 zkvoBxcyV*#4dYjVQz5CJor@k_>5lw-YrXAWDT(A$MTf1Mv9F&g&RzLM(&48YSb>-#o5WH3$5=|Ldv;=H` zvaDn374=X*GLK*%uDr0sZO*lWtFoJDDkc~Q`O-4HV4anE^C zWfpX#jtNG&J-?2A2pKY4HUzS+BEOMDO-w`y=c*^O>X2_|?F27slQD(R$EV2&agTOs@MC=+|!qTQV>gSv|GNs-ALZl3O0tZ!<+u za>he0&h9k`53Ce+{78`kHTh%(gJpHjkZS1(9U=OZ!*x;XEix;UQjL15g|-ktR@Q^AY&^8fr6J75i6y&&*X$@JgTW3F==WN6kX2sjHM%+xfz?SqtYTgyhkE`PVoIiN z9qye!lSdr4tz$deD@w?mmy?pjeywUIZIo?}eUy_!VVg-g6Z;sDlXHp5R&~!c)%ln$ zKq1%l_>g3Lq=;R)8)+SR0Xlxeg81ZbT4ZcuvjZeldXt`wEm!`5hGM_hs9*1KjUMop z7+)GD{k$pzr!J@6nA9C_E<;+6y_ThVC|#AK0o7~M%3i83YrERvdV#KcjlwT-b+mw~v`Tl@+cPg?G;Ai!jYn~h!<*fST66V>)euMG2w7eUooQp}*(|9U z@h|0rSmKS)zUx@Gn`7vI`5`bfcr(TAUlFC|l0w~L#`2%9b&LAImh>$(PxA8fKbuZ5 zn(^4w#MMyj)f}%H^5=k@v2K*Fic^F@)2}$ZE*%1;*&5zn#}Jts!JJ~8|2)C>TSBPa z8fwfhrwOw7`;u`}vZ_t@tWDy0`an`lH_?yxPCWpaqHx+Ku{y&^5*Ij;@kw@y_-AXP zWK|3LQ;YjV@A{QEsiW4D-9V5ftJ1OBiREVLcr>pkUjiOzEv`BKXw}qjz;Fzl zQ9k+1w`xC)F_`(I(Ry71`|BKS5dC;U)UuNC`gN-C4;bXl*q}!37Z!8axtL$mv-OjN z1*kW5(D;M0_-k5^_@0~8!6E&bl%^C{ako@jFs(3#tKe?$K13TfMd;biFXCS+3s8IS z$IVrI3w8fu6%r}reb>*askz@WtN1Gg;WCkns5Lv2z^7%+++?@&#o&un8xKp?Q`C|W#_d@-rmPLHp!EcBblT6twnZ1`z&3i}qJEq1U!yEQ=g z90)z0GDN*WBgXAi2!U(#I6@li4>;*Ios9?cV>4Tbfh;Ab4|pK5&((pI#2uR5ZZ#vT zE4)0*?tN@M#SC<(Uq|Vp?xgUh=452n{2`BE*|dEDI@w65nTk>;ao~Yw>v&Gs`->c% zP=fkJVYRWj7Vc!OQUF{ZE0g`kc01p$ZECP)u5@Z~BfB{_MgR-U_D9I_^sL3JMkm+; zlQsOgwvvvV5Sl4OPN_SfCq+OA!XF9eoXEDf6)A2Y33#aIavN2WHpEi1N-j?aWbd&U z?%f{exiwfc@ARg6jBI=x|Jc5}OevoIwdb>O-2S-P3GsK07I|kIPFyejq!mb=2KGSP zl?8|cci(?#X1HhpDwU`4AB#9S`fKyzZ}<96O6mf^-Fjj>O|H=PjQl-rpir6sAfFff%8c%Rm6 zf43JnqFWQS;rEoB(w-q*E;HmGPj%Aqcz?NWE zn2KqROPKF6r3Y@-;VMLm8VWRH%*+Tg#I2Y9MgzBuO7 zQ@Q`*RugTKmA>kbp0IbV$N+wW3738Mn%5dS9%kJepRUQ^4y0XXfAYlWT+b?jU+vwo zJdhOgR9EKi!p2U8Og#3Ri?Gb`-L6|V_L8i2Rq9m_M!O@kb2?JCUU>HUqI{gnHj%Kf zk6(6j^U8brt?O|)KVZ`6w@!{StP*5l=<+#wk3v z$kF#V_q8Zh3yM{-J}5MwPwe4)*U$a#vc&OC`e9|&-OW1p_Zr-6tyrt@Ol5!4uU45> zd#@u*#!|A#5em=Uh^j{IM@5&Oo)F}0-Q@ntYg55%zgQiO7ZnClJ0|;$t_^iUE@gqzP! z>1(FPwWAfsHk-;v&#$V0{F`JX69BGCz<-h_J`{~4mRhn z`teI`?*+y0udJ)YKcy2c*q-(6O0o1+{VYZ$fpJ!Rj2#(x@b!z)Lqjh$0?a^q9`|3*CnbpVv-Yz}igvJdQ`8lB6UR%bLev)>&z%|Bi(QG8>Usimo8qew~?NsIiq?cNU3 zYSnTdaP8u=tM86J!bRam%>t~Z*=T3c$nWQJS@gsCl_+c+Xy7^gW_BAr4uL%Fd-DC( ziO6s1$EAZQ>_SVkH&+1O`cZnv@UtANzw7*QESdrx8#4`!S`f9#pN<85Di7jZ)< zN`nn>VNNqFbjkceDm$1dz2(G74xs1i_ z8U+>F+S2_iX4%|~%nVug9ukwcX3jN0fA;DgAdO<`?E2%51O3jJxJwflvJSB^2@!&# zVTUdPBUms&L0lIHo)7FToOQ9S*IP{{s7XZ%U8rOB70CeJx&jerad&3{-w*3=NFp2)gm zbCX4Cr_y!aYyn`Lk`vHohMFU)?1TD2e!un`CG47t3>qv(!h6mEz|)# znhB$z&=a|_dBlX&EiWM*N5ktmCteuNE#PsS{Xw6lZg%Q?pJ%Qef_BQ z2kG)Sb_e08cEs4{3aR|BhxJUT78Zt(n)CL`S36(+6c8vIpS2TeX0zvIJ|d;5XDyoD z6k=bvEVY+op!>utQ)i^LvZ=@gNT2_k1KUV8&D0W@$q-0uoP$o@dM;Xwo1P&OB+N@n}ek(}`)6{AWT+t?EceS1wCY;3h}T&%GZ(P3&|<&3Xc|NnGyITBPVsp zdNCicteYTPvGa+s<5+|!%5G0VBS;do^fzBy!Tuiw`4P8SDL!Z1nb>vv^K(iB7ocj@ zy3)Z9#B2Fi9e;iNH+kFH(KM*Ip=wp?R16~WR)ID0{xG)~&@cD7x z``yPx#-d+M{`^ML(vn|m`Rqea6Vn0x+|}EXE6W34^K7d`e7zhwKEKy9_;zhDm#>tu zu*kcg2(djcX13`VBYPGgF=&hX9n7zZ_#6wA@~!O~x7&SJU7cBXj7= z%{Hyeeivct>h;L>OZ&lMwtVaLBRb)FJkb%=_2g00?6^rysW0m}X37SW-HLKn_%i>D z94_hTmFK&>T~^e%8+J4V$ck|c$%joh0y&lq(#a%ZpFT-<=YcB{IUh5%MFJ>n5!OD< z*Pd|@;_IxLW15p38z_X`#W@s{tr1Xc@hw~s)LnV`~BbIDBz}SJUGD9FM2hb>M zB)+6^SezQ4%Dn)!$bi7&_Pmq3K1^nlfHb3-`%nx3CF+y7{?D9 zS-}^E;KH2=FSiL~9)uz+uThfiH-DP7MsAc6^&C(cw z)Q6ksgXQv0AdlJ5VY{0r=8tU+C5+F7YW-Ajj+XZiE&E)KIb?P6jd2~vgZa~-p5}uO zAlK#kj!uXDBf+9Zw*|$gk)AfDcLuz7WJ?8Ge&j5%w$GL5JqL+&MRrF(SLcxX0JU#* zEkFpV^p!WainU5|#ggCpXz9PZHm64#u2j-Tq-0(coUF1V$Im(23U^+}PrAADa@xk1 zDkNpiUQMsJEp^OoEkeEX+z#T??4shoFZVWhDW>%7E?OT&id1=0_29;@M*}C&xQlna zwtXBTTUdUq-E}2E{@!`(cYM-NZmm;t1oVkk_7T_T@dzYmP5M?`51<2{o1I8nr@T6f zGKLFLYV`Jf8yR+n&45~o_E@j=^ef(I?2Swu%Kh2H(Ossiss+BNMh9&zI6bH*l=^!> zE5@c=#O*4Y{LFRr+azjOd_e*td5SBz$|>S*Ytj6N?hB(AB+pMuCeeoXccV^5@<1BB z`3KyY+9p{+<&nY8DjJiq@rrBndmq$JR6Fru*WCW{BUWx*P}wf&RT@=>-MzV!YG(Vr z?1B!RHmaG7*v<6K-AnO!N0mACu|m_D=yAl&mE>D-{oyU=TyFlccJFurSET&tt=y@> z%YDXOdxrA9UfSvBBXY!59<8*tTV3Ent#+J#Ox2fzOjbX*{b=WJ$55H0V}4&=o6o#6 zG`9Ec_Aw==QBw(~Fu~e6iy3>iS6kX#p9E!|w~e$f(7$x-n_Hd0*oWZvTMyq9fQnWO zq<+P9qvVGo0@Itf0(Jtr4hhDRNHMGY4VkIVl?&;;bl_ch#n8>qXs@; zdzAts%a+L;od85r`Q;(AjiMFCaH33X{Ebt;1$d57K$X!7+smdXESapp{|rEp>Z&;P zxMXGMmTT<216fI+X2ai_8Cc*kA!iG8ETkdrH&&LI=OAv)6x+}s26FAl=mZ-*UOut$ z_Od2>Yvyr#$qU&R5rZI$&aJQoBs^2lh^&&Pzs0WY&lZQ)Z%n|i_Ug!=V+KVheOD;F zH$La7Unv<`4lXmIw@LH4<5h!tTy(8-#-};`aIIwm)DthFptt8VDF2I$fZVL;0vWw% zvY^vqlQ|%xBfKHe2|%wo=I#!LZt3=uW=@L&;3$xx>68TE8slaMErzb5!ix2 z*$sQ(c>umsL+P+OjwS~Rq@DqT-+%|iy-ouMXJlvKoV?`%l*;KiH(J_ZJh|(4NQC3{#~CJ+Zq{cP#%7v!X-%EQwj7Wb^)xxZ<#LAW-j?Cr6S$C~())$WF*Fp> zmGC=s#&ne`{S8DFZ-#@Qdl{&zf(w>554VA$DF+&B6)0%g+4dvvckoR7zY_!u@#X%G z(pdrjd@6SgD1!mDcYh#oKkdzOV9#d$N;a|fT>kuw<1mH8arkPG7br242lYCjW~UQW zVxE0kP8?V2cbpa$2emp5#QzA$9zpr#Ywos8{FHX6J5Y(sbNqkpm=C= zCvj~4W+70fx{|yr;IL*<{F}W%8PXs4zg_wVb#W_Ehyazee-H;$<^4f|TSNcX^wPdxF&&xS@zv$ti=H|MWuMQ@4aaet~vqtZ3*Y<;+@YoIKm>jR9??CEeA-1kG}AJ%($a^obJ~XNudJD? zB=KENQp6jb^ww$$uEj&P6;B%78Hv#~;UTW3=1vjId0ZwRo3-&~HK~~(^#BKA%<$Zh zG#ti|JQ7L}e9CbY5+#7=NSh)P4|U2amwrdPlgeOh&Xfh{W7-0w>bS|wy9mf<-Wlmx zx_rhWKGYkc5c)?CtWr~U8TcHn>r}guPh*fOb2_iT!f*0QWkc_GZA6u}&p5V!d@wlU z@$ql>_ZdkYK5z@t;XPKQFV5XscB{Zj<@BQwq0zm$bEVlJ(poxSklpUqzg})HcdR!g z8}_4&h7H3v#_Y(Ls|XRJ93HLG?6(g;e`Jf4s^z=$D(pIDKK)+~iXYUGGb!ZUJ~xD= z7#%`9HF9u2z~0vcJ@vXn7hU7sl zGK7~?MgPE@H_{o=Tg%60x07r>--^Er3K8NWVq~uRfXYUTwjIx`VH$)q)FHMB0wW_5 zl+^uD0b{lUx%pj3KmpC7OB(Mz0W$j6pV{=Bux4r$d@iRcqgj>}9K|T58#I0X+?~lo zPPAr#utgjXB{x}mW6n1D`cqh6`;lj}JHn}P^GMqB^&rk>n%qU6E$#@eeYyAq8t4N( zZ>#(#CO3b)0+r(`=R}e#7hggok&e?M)FEEx%hm z;=T8?UvKT6fkdsdAC*4sH`Gpy3%0v+G+ww}MBLzv+=p#m&Ypx#qxjDioL_L)R5w5_ zN1SDw+Bs5I6n*}#toXAS^q8#w5~nUk9G(sUw)7cJ-MJKi zuyn3Oqsn{G~Dhwj(Q2Qt5EVIQ46KFeR6VWL`BQgQt1XiGPKLC zNhNd^I=a|0ozp>7pv%+{+Da@^F$Dp1N-`JuZq4KS?g;aQ>+>K)fh;ClR5b&m9%Ot# z)lT$>8U;fs*MfdM?xiXY*b?S8n&&nlMCtLTW`^z`eo8Yvb{a%^FM&i&+PwV0(7CqJ z9saxw1NaV*{!GuZhE2Zd>)90qO3=<+dkPler0fE$RXR;BHiYcI<`8S;z2>Ids20LS zyffdAEMl6Yh&mNW|JRrJ$L-%ce4O+(9{}q2aUW z@ic>Pq>X9hjiUVqxuB%VT!-+An$fgrrrn>ya=3~qec3f*BsyXC(^s{~ZrmM6VTLUW zHc?E+1M>_}PsE&^sSJz@r&MIL$684&LQ45q;T5ql(|Ay7-Ed5-NK_g!eP$b+a^@PC z0Kw@#6Ciet6w}Oqd`g^HtvXx<<{J*ox0mlf%{qL=@s@U93v#BFO{T)Jy`F);b3qY~ z&uyE0Cm9J`x_d|;Y5L{KNS;Ed+EErsfy)%7rGM8vQJCFYi?}sEEBsunAYW8wP@_-^ zJex9x934(>1}4m23Xx&KV0L^f$(!B}#8j~!NQD~G+(=q);E~8zoRU1beMBa#>-zjw z^JgY`H`n~I<9MhlRM0&azwaYep;mxcr;nquIS%K$rC43aWx;+RmrmULZeQbWcRkE< z&}z`GZ&tpScY1G%5?B{?mN?A=oWzyi-KLelnsSVpSu9lpd zk+?odaT+pz+Gald;;&NiQQn`CxWn@&Yt#GklW`r#g34)U)_}D*TA+ABe7IQK39J!X z1y*{9!i5#uRsmooMlA7&(;RNFCsf?uh>`08;N@9ZjLAe9Gc zD56v6iTo;R*qk#jkll`|{|*T0bG&Q6p2BsDe-=1ZJ3EtVT<@qseEDT0e4befc&q_% zg~(q+Yzayk99e)QXf5TF#E zB#t^G(aaYWc6##~G`7&}OACD1Ka+7pDzK70>pUijfz-8q9`YdMzKG~ZI-+fH6^=qx zT)x#Iw?!yPEuS|Fv$2VUG}2xHjDsbWl1ms7{Ed+{Fh&~xXJDlK55oUNvPHIGo2(Rn zKr1<1Kko4UZ^Hk{bdzGTPpHJ#xQ9yfsS{(mF=-{wKsDE&S(G)@46Pp&8!{e`T~= zJ%C2}m`Vl1i!;7WFYDNsy=_z}JK|tA90ps|;*uI7btX17`tR^0Hs26$%=MVuOdU{s z`t#@DjvgUb6j1L#5zEnI!j=1C(Qs1+t zsSbIeLQ*0iC&e!s2exGZPE+jL`BtYxAs`LBicmOvl;IRDb*}vRUZz_W(>2(TC=G8L zHGc+tqJt-_=J|?-Z{|Ot0a2#{MBVA1K-AT-)iePSwXy~kReh>2yU}GX0l~f0A~>(A zDf0Cw*cxr2W&GufKZ;>Y13Sh*j~&k4S{#2l*LZ9F(VBl$=tJ1bY_hX8OR(oa+i$DD z^SkB~hYzWorQL~r;*6Zb9ysSvQ@XCSJKH1G1eiSz!Ut-*QqPgwE^51;xYc6ie$P^> z^}Z&TdgZMXArZY`<&;U#RobZ|xS=lMh}z`zzT-kzNkNPK(lg@X7hiNr<9)47LG#Ut)t=*4n6|kC_j?_)RNHYqq;O_&{DMNe5 z0)$f+r`ssFm0e+p?oPPgPTrhO;^h<{!w?)5!^;N>6Klffsa`ahm}y%vX%KX~&P%sv z#OY0k4QQlx|0_c=o;<&nc1mJ4T6RXT7v+!+mafH)C{n2{#I{Nh8FB~HLbB6wX6D|K zK{S8!Ea>dQe`4rFXWXX%&fuwP@hfsA(*@7H{Vu0WeB3*oZG3)E`Lo#p1MzDb=cL}A zS%5SwKHHPtHbPfqBS2L5uL1mx+dCeI?(Ni-yU?5>yxz)PhXJ5^+>l@-6{LP&`;!MP^82tNW-=XwU+RHsU@bs(VQ~v_p z7ob9=)UgrH|ADmpKgouU?N!tSP?wuez-5MV9&i|*>PRW*aOCISVUzNqWA&3mvKza* znm8uRX7;rfX|+Uq92Rnm4bzPvw|y9Mj`?w|mSKq~OCd)ssW)=qcU5lFZJyOh_|H!RQ@6E{?S3 ziFH&<9dPu?5grdufn_I{>^vX{$@JaMy4RI6zZ zP*89c5hJ?DjBWMFqSqQN*PfW{<|%AG>7R8N3*D(GNdW5;nZV1e#dxz_gQj|rh8kLv zpIB2>os)neu?QWzE3EvC)&8tz_AP?m2 zXSQ(yR9!P!w@+o(7=ZM8h}`OKyR#{2Br!sq`g*a#r%2vz3K#^`}|hd zXgekgjc8LnLBkh1(}EIQsrYo3`SvE=3g&8UH{VM>a@`~;F5|Z(6YW#FA0GAHmo(+y zzx&{At}v6>3Zb+rp$*wzIVdc%A=a!$dAVQkrrzO)DkcM!+xgzVV6WWvW_+CLQP}!@ z)HI;`4vWHv(YsN}kw<49ge7;k`!p1{wz4L?uB&vBFD%#=r=!|$-&KFq`N`UY1rC86 zEfVYApYQ`KKKUH?aqG^#Z*eV-m2e-u87?*>8tPuqbT|9y#lx#-?67ZcBEFRDJ;-O; zE7e+L?ry+z>AdDKtvV?~iCbtXjk5lfLImlgfv=)&ZMX3w)9$rlvm^?u@rB zCAZ=>2Q}hc&>8Bdf}uluHKlTBMU6Acy}J0`<3E0ihMp=+bqn(?ju-n5VOlf<-{Z8P zFw4h@J?y&m58g%Wii%)DIzM95pbmW9aO>`Il8Udl7Wto4TM{GkwXo4pE&YKA7Xjw$ z<>RK?eTL(gqeez3)vnXcNe##GMZt5@T0R*|Y5~bx7%BN^%A5O=r8i zn~qN9Ks6{(&C<}_;1&VxuWG7){M1BG1bYDxOOnHKXDf5hN&}5Qsvf3pELe}DFYYwGx9 zrs1oRS9^aA9aM!^3gU`8VI6&6!e8>r`E81vr(vc{sCO#7a~f(mzrI?hfbcT8?kcG>3{T^V$J;GbhbU}<^>9SP8K`cDNI>! zCP*>Ry^X9m5(ik?<*5P6KI1~_kk{a}(27OGa6l$4g4YakUE(>k2DYIrAg9&wR~^gw zBV0|}dnj(QkF&aG>sTV|$@zxR?$_y3&G72aEkGs@4qDsjt-b1q2aG+0<9fEl#YMwT z`Oiwkh}BBN#SGsCcnkJRdh{a@-{GOpwVIfF^B@q_hfN7^(2M*&@xZz_L(>aRBN^=2 z#t0h0;LsP>^F~(1>C}aS+_xh{{`ZnsYX?kk6q``T>UAdNJe9I ztjteXX3N0>QlUcM;5emtw&T?JxNMxF$e}D+Rq3@?4q}e6k*wP@uC0I1oI84VVt5glh1gVD!vriKJ?I{S-MYNl5zqjxbddlTG$_)eq+946M3!@#g+*B_)wK@H_r7oJ}@6NB*mC$lwOo zk7V21-1$7FtPgd$t0_SIy{V$a?wEnL$y&G+w=dw2dlX`1I-`$Z$X3K~(&;rbJfLP~ z7}U(1?Ig)KM;EQt4Y5D+?nGmt5Lnj#wK8fP8o)g552WY*Y#xF1laX;ABZ+3a#SCu* zBKc|L_v`bF8sZxxpaf8>lSVM@uTTLNPYNt~QLO>kmD1lDD4Dk%z0`PK&P64Yc~Z!8 zNHIKhbO%*oQOP7A#&*IJUb)T7vT*Cq{^pzSL+T*Q5Rh$MPADftdS^6{qwQKrQVc1< z&^zd~Yf?uYL!fA*FW+8X2k+a8B@|jS+uNorrm~l^ZjaM|Rc2?7{n2Ov8U3v_mi5RE z?LYL$AEAvUaq%BK|C2<-6(j>CR8VH6p#)f0a}*`U4Z|y`L4lI(*lU=3fV9-dxKmi` zw+SpIkTPn3ENyrhTR>s!iJBg==>`ktQk;I8#~E#iBZHNyB;n+SM#46b3|the89MY9 zmN6*}j9`wr+Kx7A90c!!6{_XN4~hOO}eQ`#qriRZ?W5ml4p^@%d~}(DrT-SUX}z zHuxlJ5fThYQiFF&rwJVZW$6r*8B?*tpdlLuWckb$0u){lHa?#sJ}$R~#zve(iMO8Q zow*{sh%s|prB0I05KZVTu`Z`=S_C%@HipOn_=0;WUw|&;Bj6@ZfhKM;n&72vh?({P zfqS@15bx}iFY_@%Bil;<_$=d(51w(N4v}=GmUM| z#v-fEf|9b&3&2|PggeRvJQj2fsHa#BgM#2^$m$M^!yqj3I5HpR0n{Oo7F(opJ9~UQWR_WJlTin;h)qUH* zntV~iEm%qq*a!l41rp#4Fg)KXb#iyZ8*1b~FA*hHZ-$MVJP??5c%e2`T%JE8{cRoK zTs07#FoR~oH3yC0b^kRo?r9{hdNRXtW{OP?$WV;FBB(j2$9=gPgkKD*L0u3v0uPGI zp;^q++(1xJU|Lq4ZLd;198*b>a(>jkQZ6^o6~2ib|6?(-ei^)5%O+S!tZj@qjE7E` zSXs)c*5dI-ScI^jMFkF(L}5L>BVwOzCrLOWh99hsw?_IHVOr8r&=;GC z-0QjCn$AeO0;7NaZf-XMro%BgnXT7bGh*8h+V7?S?$6A4JwZXD4#j(C00LE@YAWMp z!Aj`YsAK%1C}^JcVeRW3cDJOaqM*%Prt!<76b0FdQ?eU@Y0B&msIl1s?S(&zf~fW8 znP1$&4ot_umNJ)VKNUc|{huou8QVx?VBNwRNPO{`3d%7akiIY7ZJYbt@sS!g^jk2m zx$Jm?3bJ2u`#T)xka%KW0);ne$(ux{k@upYjIKQ8>pB?rTD%#A;pHF@^1;RtT8mB~ z*aO0?0^<7YQQ(){6n)Au&rpwtchHhq>gx^rM-aH_XC6+Q5FDRi=t5yFgkJs2FA^)7OV))W9Y)P#%JV zR@xkl&1AsnH?e?LU(yoJT?E@gFeNpxpkDekr}@RYBe*i72~WWaA~JM*$>m)x;E2f7 z;WqrkFJ@7r{nR42`7?;Mq^?kIz*-NqesvVv@IhFl4q42L2aZGxDgm*%W6-E$5R8@Y zmslZ4|H3;it$f5ve0lvG$xN&&;x8ta4aIS;V|mlJQ}f^S|oT7eQ39RC>rs-%7fqw zc9LT1#lqYH7I*OrtDj1Ml&ire2AS|i2FvRJ3vEHI)RJ?x<*S;g4(;b(8?YHnStv;M z>#bR`+H4H5#hDOBBP$*R?jTvFYi5?97#t>^Kt?9KQI|51g5VB5Br`Gr8Eg#Iu7fGj zSu!*22j{(CmQJz)pS2$2h;V2t>UWo(B!r^;rED2SyJx z+So@;S-|WAuQX;oLPx2QhTRVH*x^5S_F=Nw0irMJR!=Se+N~u6grX_T&aKzYRc68d zjX;{HAR5U|Fs7D6nV<}4!1MR6ER0Eg#v6@@^d(?0oE%VqG`0ZVe};qL42o{(aGrGo z>kMNurA^9lcvq0RvlDz!29{CkXxYKc=Q*Bj=UDW+%55=##F7@5ND`W7QAzH0v4i(` z0fK4qC#E>i-uVb#I}=MZf{^)~zLySI(GF`di)9ioky;weW!)sOMxYK@`s_!y@yyEezYlS;b}nfWsJ7{d9HfY{F(~0cpo- zH7QC~y)`if@iWBri|GNnXeGX?BWc~32sb^%xp9-ZjIbu0(SjMj0awe(PA9_ z@yjOr86c`a;3Kke$7#?FkTj@<9+EWJFDK*zOd#g-+gf8^p8$IcL7iaLem8D2=EGW% zcs(w#j?;{r9x7r19>(f>_78Bc2&;GfH2c{CI0F!{k%8_VK&m4?Wr<~7)yUaXEn5U>-{TkjLq;yb zg9M0|4_I8F6Ps*piMdt-0S6gOV7Rqr29b<3GG+M%f!xi|ArKa;y#a1H$ryO}V81Jv zujq$VXe-`$g%rpyrHK$3lIjR7sT|7qr+|YPvp5kH7AL|Y%9y2q39gSu$jNAWB;EuV z)ju9d2ElkIJwDabzdU3P0n9M)eADTCZth)`QZp!GN#Md~axc>jn}TYFmTzp1yYhr# zOfiLl9X61_4lC#Kz`KOq{9PS}2OzN7nC{Ty_Bg8f#7>~Yl(MQDpnlnKfFVxTu%r#goYESKhU%z^rgDr#IU e2eD-xY$QpaL^u+u8jGCI1ZuPT&Qf?6KK(zs(LB=t literal 0 HcmV?d00001 diff --git a/docs/source/assets/images/copy_token_3.jpg b/docs/source/assets/images/copy_token_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7c4d2b2ef02151200ab974768292e7e51db140a2 GIT binary patch literal 44375 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 literal 0 HcmV?d00001 diff --git a/docs/source/assets/images/copy_token_4.jpg b/docs/source/assets/images/copy_token_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..90e33e36bc30f9a75dffdac145d9231b37bf737c GIT binary patch literal 24288 zcmeHv1wdTMvgpAzK{9BtAi>=oLhumWA-KC+$Pfq;T!JMy!6m^pNN@=*!5xAV+~*Iw z*{r<#?%%h2@4ol`-+^YT`?OV^s_Lq)ZXll_^8ludgtPdE6Bxo5rG%o~A0MQ2Y z&&|`nKY*Ntlewb-1DS%Og@=Kmqk%I3AVk9bKvNKucKMO;-{FSns2^ZBZf*dGj{X_` znZ7V7HzfU=zCSY-8w(o;9}5Q`D;F6nFCQB>A3HDbkIJAt4Ojq9fH~j@7yt|a8K3}# zEC3H^&hUpa=$nB`06e9+v$H)PGqbG|lYz0Fp$U_boei_Qfju)T6ALpSAna~$U}R_dm?D<&5$kWP**O*#Z=oYGgJDp0hzmvwXGAMyCC@wz4Jlgn_^~iApu8YQ$D4K65nb-bAsgG=Hlk&#^lD%Wantc z%*xBl%gn;Y%*MtDt-jI@vVc0CQe3<7WU2-cD7_U9U2(gxi|}wySP{w z^O+i$avB;Nb21tlup2Y7vKkvO8W^*)G8(h98*_8A^01k*8j^n--q`5-==Ls-)<2AG zY{YD0ZDM0$>+A%j1}if;^N*AMSB(iJ(f2|BA^D*=LhJB}Ihq(an>>Ujp_^CD$il(M z%BAwt>u2KT;u2u~O+5kT8)E!oqW{{N|1N3?2^bsk8Qsj?&hf|Ll}#M}+}pOc5c&@a z=0{YaPkscTV?Y@Y1qDTb`Ip20M|%8F0_8twyYJizZAtbQeEug7^0UO7*h0nJ4Jyx& zDL@QBLO?)7fJZ_^Ktx7DLPo_xgVGok2Llrw3m=D&03Qb*?=A`Ty}QIz#CZ7ROypEF zwDb)0gk;R@%yewjbo6vLoxmU=Bcr0Ag3!=FbVT?>bpPcKqzS-6hJ}F(go7amV6kA} zuwWpq04a3!MS!mAHw*i>4-70EJOUyTG72ghv_b_Y0Br;Z3lE2Y01sW#VZ5Qw0eCC~ z?0c-Dh<6kXkjNcCY|o?8ktrUOG~*}@?oqNEI{KoZ;^N^G5K`TzrlFB`u?@qN=8@p{ZqLY+`C=Zei)kK72=`bKu|*Y;3>@x|vpq)!J%~y# zX-1)7SK7ldbR0y*rQ}$k`g()f519Qv#C-n{X5SI}8LvqI9S#Qi^5C!lVc?v;-%eCA zW!5({e_v0l)mESfjjZDu^m&Bf2ZV=(#A3AFQIV3KI!Xl8 z-wGwg0qM1>v`lC}D*7XZJH}y*J4Uft559nwOZ|MGB^GF84{N{VIHo+98c>8*%-z5* zo`1b#WuA3Q0RKP%F1DXMgzHvTjn%h_�_EgD$H|1eZsFc9+F4)bgM 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 z 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 S_bzt-j(Wi|P;7-E6aNRy6fS8joo$M zZ{OPez5B&~AHCeU)91{YnNu@YAFpPC8!{5o5&#Sg43Go<1Fq)a0C9IqGXRj21Lyz% zKnCDp_yJfjgarUFGS4glcrXkD{?~n-_~i{y0RZgqIuU>b#-W4XLf``st%HBQr2h2- zKOodEzL9PTSlu|eGBg~R_GZj6lh8iwQH27t)OU&6oO7bfWo zO8>&|FNDR$!p6bJ!okPNMaIg@$Hv3Q&I|lj97xlECEx^D0FHnmzyOc|ieShR@BqJz zzQ%zg1I_?=Dhp?4dp>4nTPG$%6FVbQCSyArW_Lq-W>zK^WqPmzf_tV;#*UWu&X#tzWM3*YG_rGX7Nl@-u{7Z`Gc@Bg zGBM#~G%{p2VPs`BF=R9}VP|DDVP`kt=49nzGh;QP_};vU@z1T>yEs1l+O~-?v*|-q z8&g|nClDK~%oNPuI{p8uO%O#tH~O3C2Vn&B@QFE^8akWa10SI;L(Ry-!N|&``qS_; z@o@19F#k5r_@Sf!Z+rePVM|EB#F)?cOW*ArzcsI7>hQ-#`=O=Ke_3F@feNO4 z3xI8a1c-uyBEbA(^ZykeUt>V}1IztPtzb#AKjHU3xshLHys0fX&E3HHd^G`x0Z0f4 zhzRgVhzN+tNJz-2m}nr5QE@PCpkw0U5a8qC;NcRIP?Hl9-zLVzqhO-AO+!o1KuDt04BUli0^xOn&kx9?EX(9&^ma&hzU@`>FOmync_ zmQhhvQ`gYc(l$0RH8Z!cv~qHGadmU|@br7?AMosXU{K`CsOXs3xcG#$^o-1`?3~=Z z(z5c3%Bt#`+LqR~_Kwc3?stPj!y}_(A3u%H%`Yr2Ew8Mut?%yb9~>SXpPZh3feQwJ z`v%s}ko^r@m>^uR@bGZ(NMGQBfpr5PI81m1a#lnvQ6(fphno~^PmrTBqfoLd z@8B3Y4xrwm;+VU=`vtVGko{|b`Tj1*eg^CpxW)l=I2ds7;4lGU;EdD@;tqATv)d+p z9~lJbZC);%4m32?L-=H9VBD&X!&W4Ypt~1qG${zvGtsU6fr)yI&2^r~%AU7f1rkzAOXO`bpPjz%*2A4>Yb0vaV9OK&Z@n zOijId4Nb=*U~G@C@Rw0Zx5;aEvU6cK!?a|G=%o_2PhGrZ?K;XWKQFG*($Vu!xRH2E zlqlIl6dySMfbhC0U-|3MR!bGk8X)Lyu<1?e=y$G2#nSVeyBn9=P+xobq>AXE$nV_K zwBAqt7M5I2L!PcQ!5dW(_lG5Gew~ftEL_K5$Uth}!%AD4i)LNx4T5o^qsQW_b9ULt zJ$g42KYwGl$K5e`=C%x-T}dTDa?2u1d%57eTLRbKl|dMU$| z>|>Dfu4D^Ze5EHxCk6Cp^0$0VRL6qlONF-M#P5h)0p=gnqs9-O6zO-$#q5m_#NUah zLW?n|zd>5B5$-|c%ddzy`h_Z2zg8h;=YTU+pmA%nkv5!mCsT+S3ub|^+3$t{HS=g+q)#%&FoPP}|_$ zqoF2_CgS-ztICqzJ>2p`)`pFPn4a;;Eg}?@BWIfAM3)z>7=`BJmI1^vUK_8nYm>zH zK9nM0O1|C%ypj}Do~&uCt`f_ew+a!ysVWIt--Hj1++=k_m0Acn>37l-#D?ka%$`Q{;~ETUq==loXrQ2(6&|8; z+Eyzp@6i*O8n;p(pOCUOi{vU#TJ2<$`n&)`Xd%pnyb!J+mau7&g3M4jUtZ#i=H9C$ z@kE)6mrf96IXf)~p!p9zwAUEPQ&3B~e>uf&Xa!^%hZB%SI$OE|1oM_u{4O1@ zfTR&eA37y*Y~cUmqfDSJCW2#f1!y;Iqb8)jytKLk>glY2NQ}cCsF&Ki?4NJ}sHO$c z0Kfamv(jjlE8#}G6Sb|XstjY)i|y#$$JVKQuGc$SI&t5|1vQ^T!7tiKj%rRNFR1beXxR999|PC`K?+2X~8!?5ER?FSK+$Z)8TNH(y>x+o8p{Y0gWXh zw`lT?ssi0OhaK|athpmkw_NERPI%IwtD0Knn}kwgbac!J@o{uv4`YZvAOz9AIW0yl zFTr~q7SC-v?#(`(=(i?F?`4N0Z{ESdhJDe#?yA>Sytrwv>f^B@zW`lXN=Y?#8I2G4ly zEVE*2z8%H;BXxAA)yF@!1R9^DNR5+_KZqoJ&Q-=Vi;>V!-Dkd`u5qh}xV>-DkuZKC zU|lPH0;>%!S}so%RWHC%-p$dW-T8BB03IygY!JQ}tvXCp@C^yklQQ5D$8$J{Re(|6vh{q_1 zGn6I*Q)oTU2X~cU(eQb18D99ibTo3MY7E)KETlW#M&okr!@W%t_xW?YXBxExT1zDD zxfa5#fIB=H+T4&vlb z)W{omIt>cRcu6rQMTIA*ZfIxrrKQ~Lqx49vHe5;}=G{oLcb~5;4`9x<>YdWIkiG(% zFh^gCqIFTZr<=oB7^Q>@vaczi<|n40AHLm?y)HjLWn0WN7wZ;t_Sx$#l`V;^E~%Am zYSyPyzL2m_>0BjNx8#U!KUQVfwSBw!I=OdDr-VK6#p|uLnXwJgd%W**yWw}oh-sbS z;;n6{`g4m4C?qQ$RIaZe;ytPuHzp3)oD5wc&eLP+owoYWpGO;MQIJYk>Qr&e-3L8l z)!Bwu?h0d2s_SqJeutv+*-K@I+Z>L%kp?d0%^;(W^v*<-&9RK{+%Zva7R3b7vF?-3 zH~F$Q1@qNIas|p04ac$Q>{7~9r*02klkm+N)!ILY^~r<190;pB$m&K{?&xNaTD<~1 zv_!8R60gDq`HsKaLLS|t*Rm;ec|xQK-$_tPOD?@x)6`OwOuL;xxC|%nE#VT*+QR-& zfl;AKpU**0OzdVy*DfWc_Yxhlu#${XNVH+zu{2g?WT&fAqA zb8b}Pn6P88s)qUzvLQKoEmv|(Xg=iZnb6@GC3M1e^pIjnt$}|s!I@Jc5m8`CK*z+D zqX@f@g}`qyRR>4y>mY(Ajt$QA|At4((()Rh_G%lW(4jTcmEC*pu!zSw<(k2DNSCGX zl--hEv_m7&6%alMshxROM+fBoflk7*Bj6i9*_K|WKKADmvKwM_g+}QpKaSe<`f_el1m~g+x|4am;IZ;GEopAlppET&UW-A14Ym@vb^MNpJTrp> zM%}JnsG{?F^vAyAXQS0~gFc*(c&`BRj4QyUDcV)7+CdQVL8a;E*_P8XHgnNX z?mm7iP&_v1Lo6le?W15Mm|vIX z!}DTQKn&}-Vbab7d%iRifi!kp|J3-~g~vQZ7@4l>`E*(>WsRqIfzKNFJc|nz_r>|2 zgS?O!Doq+lS>GNWYv$|gik9~83y*YSO#N&!9_tjy@s*XmPVB4j-1Si?l)D0MzOa#F zXm5Yt(-Z5D)+pPo*vCkQVVdPg>f9Wd z$8Cz+OA^M72^Py`%ZTSROWxxW6(MEng%aD_B{r3l&=kI&Xc^oz>y1O$anxjz=UX7& zC{GKGxn9Lnh&80fH=XE`ds&@9v4>-%35&mA(UVIxo7Y7B!Th!A%QfvN&!be%*VieA zS8nKXiJ;+$U#>I}#_kq%1OfXK{EW7(tw)u+l3oN86BBtl;?eualGjLr*Djl}4nz-E(tPD2zEH& zysLn(DVbu$mo6?efijIk_I4k?`7PY*N1x!GKJ15Dz@?^@WsQC|vNXnJIo+i(51Fz3 zAVrkiraEq$QX5^?o1;iIDDLZ&7^)#0N&`Ffx63|GTqD&$5-*jW%*PBF;)z~7RC)G; zZrDVdqP1fb_7~M$E%@D7a8L1iNaf36(#1`t-CxRAgnY84j803S$yEHvx*dLJ;WNr5 zozgtcxdoKu#MDAk+P4U~e?vNel9X+NU z+_Irgu&%c0tAX$mcNn9Q%O)F1Zio!Bl5pfD9*}Cwft|ZF2UgN1jkD8X4X=Th?L8zj_V5%Lq zg_V?z^2ZygVcSS|Q=4AG9dN)DikqxICNCQ@FA0^VY+qj1FmEZa;$}BrQ_gD2lrh4b zX_>a&W?nx3NJaFR45|tuOX-#N&;xOgVncBkb!CH`hntieey3^{7YbR@xs1i*X=qd| z-SBlCxA*afVNS9)L6BNe$Fv~!p&W|D`0;* zSRCKN1Vb(4LjVk=mDzD#a`njK4(f?7@s&K^>WuesCynMC(k`vFc7M8?*}EHf3wDMJ zbqlKd0nz_&2mi-!^}6!`NQ1gn#mw~ox+zsg;Ss@RP8bzDV$rm)37O)w^l$`31y+1e2nYjl{!8U47))eC~cS_%iG$riQB4SJ_eBRlP>rNXgkQO$ArIpA9W~#w=_I?Es z4CYsWX#axFup*jR{1xz2{N)|AekL(Gp13+z*7rwL3?tR2PI=ZL~0Y<@LD;o<~RW4oJ+z-%CMv>A)p&ILs{BS?qZfIm0r6 zX|6=Lq!oXBKuKmxbtsgWpOF^+UPb{ned5#_LiLbLb7TZjZ4Zv;mFLq)e;uJQqNp?SKfL%85%6gHeT70ToxN4mrxF1(cVkW~sxEXzaENGp#!` z$@XO0aI<3+;9dhx{lxyx&WcF9)^nCdwG|1=CvryyG!kiKLYl2lk|D{(6)1C5n#{)M zN_`)c>dff_mtwB~tA(AS&>&z%cn;g_dF$;p)_ake)yObPl!!K+O|qy#5QJ9lqaggK z%-N`XHmu!pzvWJzPU=dJb{A|#M6UcaU8|cOC3*$SOWnx5IzEzk!p{Yk^DP^1hRKik ze~dQ6*Es#KrGbgOcikQl(bGJVPec44Ym9NUx3|Tihgx}K5Krdg9GLRot6dI=3IBkg z)wpvuM#QIK{GG`IMs`*%0$yH@Le)NL5YF|VUZXxQyvQ&8V`>~sNar}HakiRL441(- zsWQFZ5NF@=a+B}b#cn02!3cq3!=#FvUuJ`9=_Ib10;x(H|%I zC{YaRJ^`8OxB^gxhH@bNgm&jAGjk#Y*y@mR7mU*dn=1f9ctLY6pLzud*feb~K>Ps< zC9NyKa#(mJr0Vj*ZYTY(2LD3oKPMVB>!t?W^AV&U$ch%GCPCaQnepy{)SjcaGzSdl zkNe`k6aG`B@ZY5;_;;ZBBYFQ^OyGpg=Q305T!duF7}o9^VY2WncGZ&_e>lIXxSmk| z^2GG^P-0qbgI)yDqX8}y<1jUtP(cZrHT!itCCwx4cT6#xj-=j&87jQgcNDu{hbb$N zYCo>yFZSQc#`nJnnemFgu$<6=$rm^2FJ`c3*cq1-P@XKTM_`SY?(b#45O5K+gj5OS ztmxE9Q`QFD3l<3`>N42c6hJ3c34XBhBtgV#Kyz=S(4v$n)nw=0b1KwGB#pbx(n!Nt zY{(>^viplguXUs&hfLqcmRP`xYMN3Ls%tbT1i(`2XRmam-AIN6_GW6u@a@xKIjN}* z;F4Fb$tG;alD(ke>%IX4yn2bWHM?hi$t** z?#N>VJ_!EF$QVx-Wi?M%r^9HD)me03_JsT^{K$FQ!icA3H+=YMP9>0uh)DRCeYkT9 z;t%7EEV1~RIX8znyR@Q^yYa^}7MzTc-ZT^KD|OG4X}gw}#>^ACW6ev8A&}@WZNaR6 z+-wJIl(bkjUM0%4zkVam!6?AchP7Tzg+uD{#t})tJX@wBYq|$wMIAZOC5@xcJdHG& zVPZ~+j}D&eS+RRIcNW!9FlfBof6Wx;yAZemp56^vO>9W10_d?q_uy z4VjW4XSXV!+k3^lApjjlmad66y|LyS= z3T&Tu1g3q9gFSiWKt-KSn$8woYHg*?>yi7A>#}~b?7Ha4vKbu@f@MBJ<9dg7>kGByCAkVrHzxJ zh)#ROJcosOG;m2TWos(6wu`nmDQdW_oo))LQ=P&-q}um|S((m5qZJVI$o1IQmQ7Mc zEyOwve)m1(RM`^#UMY z{KEEeHf(zR;O%M~(exNj>zF4C=-08^RA+f0bUADRri# z#s8ELKKd;Yk@Io>Gnq$LhWebi;S5qZ_BS!3*Vg*2hCw|JoM8FsqYZqldwm7A87w?T zsxT)GN^1lUN7;3IxIfL`jHZ|#bBlTELPqlR_-&!gAFz)85z8uAHiM%)z=!U;_P7rJ z8~3P)So}@Z*f55);35&O}qWj2F{T*Q}kv(XPyo^aib!o+OW~c)qULa znL!`Iua*Uzka^*onkQDR{q;73Z!U*XA6sG^kQ^!nXESN$m&nM_eXh#4I?}%FH*Lj^ zoTl$rmn_1cu)AsAUfUm3(REX9KTS29P{&@Rl^W zwoju99&V{Q^dopiNKLauPP^i|mU!H!yC=&Q^I#KA<|ZD#?G59RjSMdJ_7`}RRFQ4b zM3F2C^E^2VLky`RCE6`{L{fn0vCmt(9KcBg-&3g0J9pZ{(Xvrhs*h;Uj*319zmq8B z6G^=}jiISTry4ByS6!iQBSn zzQT#7cek>@--5Q(mRxQl+VQx8bH8Nz?n(+QHdst)RSij-HYwWW0z~ zQ9Pk2n5T~9%xO5rZp<3bWlrQq^mYqsvMW_F&d8ghe}bc#etR zPAnh9jX|Dz&(`j-zymek=ja@IxcI!|XaJjBg6l{>IT~=H?$p&V##eITtHmG@gogIIz`2qIZWjg^S$8rC_pxaWik#C;g-|wk-ii&3pBO96f z9`bBMxpThmQ&Ga;HD2mO6Nk8rJ6z(X%5KI*%aln%`V|#U>Dqlnjj9gKR5ddcZ3R+P zSiDHNz~Hz=rMm0rP6i$5e#+4GnHM3D3QF}dNW^sGtbL?#Xku;Mcg^rAz0`_P`9AVX zIwYR)&Yk0&XF!q{*BNEaJAA4?>KU9q$HT z_koZh&52Skml8M3qQJRs1QiR zjNN#JkH39u)5zjFhKL6b(XNgVe&a}S`NamG!jThQO4#LXvwcKfVP=`ZNPa!}_W5Yz zn#y>Y+9b8+^=m@XhUI?F!6hB(aKc@R@EX@%Mt?ER!lwnzJ+jkoS=F=pM8i zLd75i0%FTC5p=6Uq$N9BNsJFEn|bzc+cl6MQlGhk9B46xa#S)$QnsTET^A_2MEpa` z!H;@^KUMC>tZ$h%i5>}h5J1Ns`z<)#2o81Fik&y zK&-VOAdBEUl#_yZ1$_S)xGXgNy892wr}+^6{2}>sWq;MYJM=#U8=UaRM{Kl+1{cmF z2{dr9qGqMHe3zvH*NrEe-o=ocm|zIDzH3eO?z`A@G92@-Dh)^0v6er2jDlV%I;|aHq~{-tbviN$bU@epKbW)Ah?X z`&OKClxy|Y*iu}1WN(#L5v0X&789HQ4R@BWZfJj|TiAcp?@jekPQ71ZXnJ2UP8`*3 ztMkM4<=m4OZIvX1CHvb3Y%D^mUs&&|l;dL3+WOH8@8UeVc%qe_K=)9DewDQ?FR0p7 zKnvgWMS0M=lX_9=R-u;g^LmxslYXW9_cb$aE$t#?JcR1m)Cl5D0BrxTbw8~k3Pn)n1t)(I0V(=1y=so=`X zb9qf#ccd5Bzg%mJ^Cok$v;PMgR1^ua>=e>M1iI+`BY!+RLCI^B@W zQ4t{=)}R@0n7p~nYr7OQKkA`;|9Lcal-#>6m@bkwwdyUq&k~6aF0Nw+*2<;rZxG<` z4Z=1i+XuV#5i=o)ia#K%>ruE~S;5#dDRs0#Q>7^I#sClPU}{WY#C6z32X8q#t@6lbs1 zj)ifq#7J7AkoDOwECzZb9l$n(wL*zYc9wi9--Ipmu*tSAho8N?q#J#erEPhO)-t>0 zO^crof3mVyvS*c+k%MF5R;N^v|3FIa`k|pXwg$?F-+c(?Pc&1C2^MhQxyiS+XM&YY zW#~gj`Z&9g>L^^d554Tp%99roj_$W*l5?5`2$F(;z89@$&g`NTlH^?5jR+o7F_gYS zYSgGK4o})O<@g5q^Yi`vA5FYTbEQh0lK?sg3zbCh9ZuZj!tAy(93#r5Op+u&26IJ& zuCk#mMXzWGKYxMl5ea0%z_&+* zsQ=Xu;otKLq~qP9sHpPTGSN&6wl#P1N#Br|6$po+n!TPe7Vkbb&wbX%-8$%5P-O*S zNnSYh95o2OfN)iAXRj{Jmrz@?;8|V)&E9p#2Za!XN4#iD>-rQgmgmySZKpv#_9PkH z=R7st4Hq^Y2oEID*WcwYW*)~2p`AdON{xqId-x5$D14ab?}%vBHb-q%ExzIk`W6*SQV?f0_fqCn3R#=(%W&iL8 zcn;MWvlr*dx#^z=AWnxIFxPOnKczue)-BFC3??t-05Ng7Ba5b*vjn04|5ad3+UB_x zJ(tKcIrE^-0ULEj;Mqq{&!myB%C^FLi)lsO6(Ex!`Npg7u4xeVmS@QUTum|IE^|-? zGrpCyxjFWWhXyyavl&)K8WdKAwSBjawRex7W6>Bh^+H^1pB>v5J3?}WXX!^DQP1jN zppQSZXR@0#y6-pfNz(8v;cx-f9dBPj>p(9Qmv){wJww(G;RKfW#%*XGmbXZ7-l3*Y zQsMTg=a}8E#&j%Jy

h!o$x`DG@Bfe?8u#;(rtI3jySqhZs4t?pDtRfB4;e)t}1z z;etHZc~;?w)KAV=sI-j_(tELi_5T6yBEpUtOv3@3&m6vG>v*laOJrwfRaGl%+JotQ zrAxjPPQf^fR_;w5NBXxaS$7>CZ=BcJdwNx+T*`TH1|$|;+#Q?NaE&Mxn0Q7?MgI}z z!UyyNRyi?l(T)v1#BFGZxmRI+$2xl{L$GBf_jpxU=BR;LzI34crc*;{ySykhMO=5Q8elwU2! ztayUrw+r?Ri_$V`L}=QpWmJJ?k?TLbzqttIb@AeVcNzk?ey@A>e~Tx$wq5PxX!UPC z;9tT~FxRk^WWja)@13G4hY3Uq+{})=b$)x&RRhbz-k>%mKwXe^-xgT#RfX#m2%ZSj zAG}i)iB* z+eNswiNC_b(=I2JYHs6nt)Xv{RYCYHCrPA``U8pFgW{(+6fQxU^GuBF6`HfHtWkSy zFC!IHG>Vq^ zyI1j6!aCv-E}zC)W9i;Ca#z>fg3m9N>njJmUXLp7RLoWT?%n@*Z7HE8=G=&aOS8?# z4*$JWbgeTPWI8Gkjb%Wd=@T6QD9Ai6RTd+_{rl)ikadQMVk&=C4dlMkNkGYUTXFr1&#YL z+sh&ayQL`ZqgYtzy6_*XR6)vdbd;A8`I5+Yt&4lixd&x#T+j9D9n1( zbDW4L@iZs!#Lv|RtXR5DQlp|FI)ZvQmyHCMCjTrwdBTmlxFo3~tej`~nkFxTL5H*C zmQbIELKT^v0xZ(G+EitENrmIJH z4lhIc8eds1>xxqy4au?VT;wki?VKJE^Q{D}IbXYDLo%vKm8eZI+X`!Lf(j67xbZCT ztg=682ON?yA2TE)Z^S$%+JT0jtvt8Gb=1z^RMmHQ?I_*Mvk-ciyE8Md9>0qu9x@lh zQ(=)lKFqLra4-BFFV z%&;4U3v~)N(C9CzWy^dDd*nYiS%>>_J$VxS{`z&1PHm+z`^T7Nt`g1;COA z*5&LWI4-TmsjXRWm(1$D`$JQ4lZO}fI9t|}ghpL#u3ux4<^t;OM5D|&tR=S=7q992 z6J3+}prXT`kadUUcQAIv(Ka} zPAZ<1w6nu0tQEB(Xvem$z(j-%iVULfp4|NDFvd^s+hJQ!PK2)x$w|U>C&Rd3oFa44 z|A(KF!s$NAVmBGo~rghU@Tqvl-J>qOC z4GMCME?!!(`_Pl5VaXVp6tpZnhYFr+ifuDEA-w`V4qO4q8ke$-AWGj}0nm<68H4^4 zj08x}Nut0vgL$Qd1bs+YvsN|Q6@bG0{MkEs(5fbUb`5lpd`bw-3d4fTwq5}x>5;Mf zP?$d9r3?Ak9*!ifB0J~}yR!rV@SLe12DBsWM6_w$VwW0B2`gk)@ts<|!u(z2W+cUpDaz zZ7;h5JPD)4PM_w_{6LPJ8<&rx9;7OmP7w*Kzl!AuNu)o&E5~+gBsG+`6+=%_ zJ-&NSHLxU-rR`RSr0BE{=S9DjK)+R&0oH0E`pawbGd+RPdR5L0_UXF<7}}Au0DfAwt_Z}4l){AH9tRM5)w7+Y2uzncJwha#iYhL$uY!Ga} z_ede@)$_Z$5=VpSCDl`Vxyp!xd@EFnh$~;=(o&eecIQsK$uOA;iqah&Lg@$T8~ZnS zw#3=C+dTD>pDm6it?73N5ok6GY)u4vTfiBAYx3h^A(!=GY0%M~8pDVi0!i$r3$I;r zIPEaCEEOmHD0*Dr^$n|3_AxJM;LN|3P?^`G!|L&X*q}W#tm^HDsa!mBWbvkDsF%yu z)6>=Nb|B)u(MR=Ir;6cb{$ud`)Xca3q0I{t7bI=EAj{Q1#9YL0iDZLW7)l^*OW;ts zkDU-*?e2OSM0nc`f&TQF%1IC~wlA2oFPP6QK0-NK9#=f8SMpAinYRmFLpakT|0KWN z?dPd!8lF*&J9b%pex$2;CY+$t*S;2Mv_NksLD$M{^xqwgsZ>>J zO>L!0wRLG0&Emvecki&yO8LXYAP&f8+xKaFJqM=Jc?==&{dyLK{B>2E!RZYb(sR%_ znyw5It`NwHboqgo4^1#%5=0M;?m^ML4VG?lUP!JsDW(O{!-@P0r$K*p1R4=XJtD~X_| zC4(;>SxiIOT&j%?k#kmPJ$s-5}qrXccD~+h{zQq+ezc=fzn%Mm=MbW7lI9TO-sps~+ zdoX@^?#}Cc!DE)LhHR~{m~Xhoe2p0%3RbapZLzAF?wBX%i09saEPmls(G@`n1OJrn za%O8&gvDdIwx8iFNT6(;2JU6Wj$lL45>`5lfMdt9Ks6ehjB2C@dHzQ*#-dSBYQ zr$@~L<`v81%_~4%S|#kU#$n;iPoFB4H@b$9UgBX!L?3O4 zG=(%#DYLzZpr@0%+0KL7{gj$UWAoc>RrCZBIMGuwms_*u!k4!6`C$3<9wE>>TBiwl z(tsh%DR*SXGh+ztu4J?JhwHf4%G<8jH;4iX|1Mv zdVX_L@5>;58EBZqG|OzsL8DU3;AJjXHXO9-vKRr3Li<~hmW?ncJ_`mTWL3WF9rK#m zaSzoknN40jIHV)O7;O*(I}qNUR4uH{H_1oDpnNOqFx;gO~dfGPGB@Ex@SV0Np9Ns(2Bk9J0Bx0vYa!)UXkJZw$R@a28V!ZoWRGm(ip<30J^y#XR!`=pp_&KRYed z)1dn#EC$kdLe%z{v;D2ziA4Rq$I2`WyjQ@7y#5Pn;Y0cpP>w;k0xqNNz*@d;;U=?6 z{r69WZ!9J6KSp8%#kBrQ8R!?$2?F*)^4Eo&8^LYe@E|=YdG9gB*8-gXtN@t1S18J9 z2&&M=Yko2Fqz}^KbOk6kfwGzbD7Y*AO?f@$IB%MLj7%bM&3n0f9h4KfXPpdoUW1Sp zg8~~aD444Lx(1E=%cL>_t!t8k!iyJTU!foSb+1W(Ra|ZG^x0q-%b8NY!cS2D1;k$? z@e@J*MNZ#Vg8zm&p@z^IQWbXv%*XFy&0YaG0qh`WqoMyZ>nAD5ym%qmBfNZpA+)RC zAXGQ^qJeLyHDOPVtFMqT5S3-UlZWbnBzk5*oOW0_C|E7BX~Axf$Y{u>?#s3i^KbhC z{E0pQKOI#b@*X3A?m#kCkt%4Gh^HFEVBF ze{rfz8xC7Nc|V7e&_4Z%`pxDerE4WqdIgR)+^k!&y3vlaH51`(tirVV)oUVXNH0DY z9eU{y@>|#VcSqlwXUxeGtg8`}(7`8(_FT|=Q(<{;AA2uLlW0?booVh&RngsZ{8Iq| z%8EpZB`Q(wbdlq`Px|q2Ss28g!*2Qnz8k9Nb`wnVmj_O9#aoa*$TlAou)W1655oadVn033Hr zPmjbS`F`Ha(n_hXl|JV6fE7D`+lG}X(2!zcP6yL1uxG6-NHD^YA0n{r&P1Z&-TAt! zJrwzX8g$Bd*8WEN_IZyTpUyb)^E(v8enp?d-QkE<&UO|2+DA38^y4B@N+!NVRSgnCi-SH~g`py|AVHx;>6I-57Uo!rmFRJKU58-Rh!fCB z+WL9@L<~1;UN^ZzoqfdOJ`xeD2i}?UTWlBwmEE9;auidz03jz`&Nj(QynC-gm*7dB zlCZHV2yqR49$&Q@l0=GiG-P~~I9l^%i|fmbto5yRkE~FOrNnoL4ixab zEj}zj2SBQAx#J&Kf3AzG!SY@&uJ+U>+B8IFEe@LC+VWeEma>X{pov|;49f;YxWk*ZC=B+S`f1m2z}DxtBy@yi zuIdxK(5oDMFnmBWC6GD9gs=8VhJ}h8$?xOP^=BDtd=)V@S&})Hk?}Se3^?(qH$=&N zGhmU)f(dGLn_6?SUNo1ydVbrd&&`P~V<5^VjJ-56J0e_kx%MAlh8gp-K_Jf^y1k&N zce&a$ox%FQ>B59h_&CZx-GPOC(c~#mwxO&ff_Z8_3i9{EQHt=7wGKg{rQ$X=3~7wF z1t@V!ivyBN$=ZKT#>|g1)7b<6f&L(;TeN>uGyLzoZbnZYD#$Ed+Z1;zbo^Gd&-&fq z!S8H-9trCe5J9aCo-4y|I)Iarf45w2l&#L{#TCZ-a^X(tAZuvHfMODM3>8KpHx!oy zRE7$4*;;7sGTIk3>sTBxpXhq7T%3M_%1K@@2|7U^eZB(j6nVI>JHHF=6hf~4s#gsk z09tW!MOx*ffP346I@xHx5hBGt8KtvjG|P(IJy-{T?l-{KvlxEpp6NHBKk4w{`)2%5f` zKQiko0?yuB>qO0e5=qgH#u6?N{Uq5 z?t)fF_zYbaGLu;>{89Du<0qFjPoQ?qN#f$3+rOlxL$M2!FA2&C&GIIK|zNzKc@YFED(>R6LF*~ z7o?{ocGS;{Nyu_>%Gpk((R9V}vDjz;SZT^PtB96+?n#$h<4ah`W{>sK#rBDt%en;? zT^Gh1aDN-6Rt?cAq?@v3O|$(xc9?n`LtSrlsFO)!*7H6Jv)E5oiXj4LUaH)+ZMD%C z8r(|vf=lh3ZcutqVaZFctUb{OjiP^V0OFin4(tR$F zzc-qyb84M2ofI9W*4Vo;cJZhDHnyGrh)ye#!QtQjpFmUpx8`Qejx|)Au>TA+^j_i; zyK#R}x97zMT<}}Yg%CevxL>-(w8wjz7^Vy%6GpB3wxHLg;^JlWKkHNvc_h0oX4Wwv zj$@AZTTbl%Ebh0Q+~I=$!}q|cem~y7`YRjXziSixXZgSTzxI8*xKiZWKg<93(lZ2U z!TZzIV=qAqUp-yL;bA~x)#fW>jAQv;KlQ~cK(k3(dULKetfxA}MAk0=e^C*x{5Sx@ cWPy!fFOyhJr5>$PHE-H^I$xxSboJ@~1B?ZVS^xk5 literal 0 HcmV?d00001 diff --git a/docs/source/assets/images/dsms-sdk.jpg b/docs/source/assets/images/dsms-sdk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b7f47b20ccda7cea844ce6b6d3baa92724e3dff0 GIT binary patch literal 163424 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

    4BYBn z88meJUjIh`u6y{M$QnMckjebOB;)mZ>k~6J#6u-~d$GHgDfT1kZ!k=6vW-m#To+~j zecO0K9^Vh&mMhEj@%!KL|KdGDkbaBGdoe_>p%)TGJZ8ME2-k45>+Ez6anSU<6QM0T zz+{N2VR)-L{k$BbulT$Kid1@R7jV1AyiT`x218~ZvJ|2?x&b56>y-7pfsf1h0H~0M zBLVUcRGm*swH?p39S*f0;EH*&X?U{jOj{;BYqD#-*nd&H)bUZFK)IE@K#)yLb7O6e zUR=3iGD6FFS}xgSHuxR4(q7n__gw(CnvBB941^z$pRs?jP-a_H11c=13(W7f8yGx34~+H<|2_iYK%WiXoQOv>Qa(^U+cll( z2|_k6Ae<7l*gTbkX)_sl64QmW%(CSIHk{-ZY6!@&S(P~qkoXKpa^_aMR?<#`mien}qj!QIG(j#Wx73~>l%(M<~s2w_;S|$WrU0iBw9_;uV-=e+aAecUaKc&obY1l$% zq@+lPufh93&xk<|)H+;dl(i#602(xWa|g4`GNbviUtvdrhUmnK#In4J%9Iec+i$Y{ z`^X^z^4F3bf_1|lz1uq&zPQwq;G%y+2Og3Ig`r+v!e1kO&Oo>rxH)QFy2)VmQ9qAE zT=}GTOU}rKpciC47euK>=`fNV9)zg$pc!M~6yuW+^vVdr!eo?u)a zud^(yPP!b7W(&d+hrb32*;43-z2_cQ+m3`&N*+HeF(_~=j1=X~q<2RFvIAT@0L3!m)n z>G>Y}viSt|zzez+AHug?O$Wlg3UN})rGal0OGdv=;H6LFv~!1*_&OfNp?*w%vZDGDxrUQ%kYCn6*)9L>VoC0n7EsRWo za(Y=6Z;Afe^$A_8nZF^5*0K51LmJKH?~e&8Gt)}HdL)5a`_K}>>lb_Qr%%g^omwAb12=72)Q2O`|NXc9 zty7qt?v?}^DSSGUkeZ4ix+|Mk;cf(@YxgnV15imb`p@D!z@_wtFRHTM>7+ zs15t7_-61@`edL%{_01W5#|DE&SN|QsZHjGm#O8=%aPE|Yz=Ch5xjB)o8bYIO@XSQ zIN|+;>A)7R*h_k))d`DDh|quhd`)#E|J7K`&tE!;^BG55Tx>!x8+>_+!H&4m32C)k z{*Kl8paV1b7n?UI>5qiSUys=SkrT)^C0hq6EHg~d1}k7BCXu(0WDL1PG?CL%Tqd$rLp9tRy8VTA9 zqT7@XG~l)(NNC*Vd6Z!EjPm7i>EW+vi%OZVo41nqY;2;Qd1vk%F$cT|d)Dc-T3f-j z>IMLR2oWw&9OX>gS9{6>3ZfWn{>9<@@0(^rulW`4(rtO#h;;`dLb8JUc_iaBgZ#V0 zvOrM!(<%}e95xQRq{Pz1IIIHVl^G{IFGNr|Ex`Du8$-Cy3c=2_ia0fj@)>V;(ro=fKl+Y z7~h!dF1thiGCw*mzd&Czz{~K4K5T_zR=BeAiQpRzVFJaLHK+zqv55Z45aNMh19y7;!pYkWdX)m+Ayj*Od{;E zXDUMsGj+>kvj_f23osAg_b~no&=59?;Pzi62{CW;Q|DL_ODR7$*plV)b{8iz8{dsQ zBR7Hf2O6Da6C5yvKiv+(mKN;!tKS1q_8L9|!u(oDk7DaZb}=gN!K56#I(Hr6S`Gfq zJVV^_{bG&aFP>t43V4qtGn6%k0AGwMrY^P<+@422)Zw!k+{#RL!JO0uK*P>grH@x! z*_xc`J#s#q3I6fT{t&47UF?LVd=ooxZ^lc?*^t?mk4QRv4t?mMFrXIH9JcV{3U3!> zx0E}kQdLd*`D)pvxdDa@YE{b-5S$mJg3(8lg)MB*5x`B9Gyds9xTLT@%WNcj*m}*Z z2?l9kYfsa86 ze_uNr45+wFoZo{46M?Tsq`&y08IR2CBws!HFnQCZA&X@!WtPp1@(HW?am<2B$;T~m zReKl;{2OF)c@s}rxoYbw$M{P>-ckhmA!j&=Hm?qOZBG=(*G1n$wqguH1rtq^tq^8tIOTx z+&5KIgMyd+X`6au-=NlO z5g9djF#^a~g8d5D;Xw#jPHi~gknb%%JOs3u6HnO~h>^S@MhPT6iRRb@Ey4Bm_DzTW z*|xgF`3UlTBu+Ti_fXhU0lpUfs%?5!y0Yzh%2f7r96QRBG`D%tCrp?U3DJIGk=!H~OA z{Zj%44sUfGJ4LH2BgK|amA z>%hsiXBimUDkI07ve{I(J|`B8RHzuiaaKQxj_eJmg}qY9gq`W;N)3AtpnwLd>czQH zHkZC1%=)u>C|vFbi3}bQS>-Qfb;p$>%Z>^gq(1{a-@<2sFP@xh$c-3y8N=P$o)IQ0 zDbLQ9yBw*l;uM~_=dc};q-R_rRPr`~ajxsx0hwf;t^I6E$AmksIajJMMQ`ORrM<5gTDh6Bc(0ywanp#S^byxu0|RO6fTg-5 z(*rA{To|>Y+fxccxGCMYGu7bbbJXrXKZ-*#AW??njUx`EYy`pi9PL<5zc{TyDmX>+`+tdI zLC$Pm%UETN>z$?jn(FjeyYuP7AW@jha5U^?nicYCANvB|OaP1v%b!8tRFxd%=WJ}` zdVDik#y>^5u841ssK^A4Q(cXdJ>;^qJDKBA7DT!deE*|AMSlz5cLj`6>QTs4`P2gu^-h&)I_G0 z8wd}|lko?sF<$WAuwj8UKlX;->CCyXynaWiU=%gH^X>>6DJ)%L!6qAw+JpN! zJBtA>?^#Z5n?yD&SnV^uOi3b|*Lmz%W}$*R&kaVc>wM?oT6(VSO8~VVoog!mu}8kI za!zsty{}?5mDXyS-+uzm6%^#2%tFpOLANWf#g2@k5JH5>|9NUI6rX}5+*?=cj-sdh zuK?|fRwH*esHe53r4}|La%+naXWWmev#+Xo8Z;AKHOcw%mkkk1GsQ=SwYj)Dh|!(U zHQ@s=D#ZjL)@i@=8v#%yxtsaZJZI4!I$!p2-g>`m{D%oNY(TO7{v{-ADc`rGY4BN3 zsDBO6yS{+1LpS|qsl}sO>YTA>7c3^e7nncJ5MWxQ#3#iagJpXh5Uj292S5^Ly0yvw zV}YZjQzTL@q*mS-CPCzic*gPIQ%*zpKQ#080cwf2PNIB{^qW_}6p{no?pxBvCGLm@w1@emDwf`NvCg@uB-`Q;ZbAU|XQeFM088;b=N z9Ru?&BO3DqRz4{bQZgp&du%Vv$e%nG2o_9){ICT)9egb~Bw70FJqA@S`LSo!q}@wX_N+3Tk6^lGW*YNOD=T;af z!a0u;TXx{m);dN1Y9cxchZ}CK&7BxG@u9|ycJQ6F>To5#jfBqTpwO1ZDkF5 zNol0zXJCoBbhzGPcl-lY=NN)Uc0TyvA>zk33V@cTiqXM>WX@|-u#jB2dh8;I&(Eq> zInsH$yuih{czrTCoIQyaGy0|J?D_=Vr?>$5o!wd`o%p$z=&wvB2(eqwOCjH4Jwk;s zE9q#NIQ}xadv`*b%pz3YPLNm8?Nyft`;KBqNUH`Uzf&8m0$Ge8e*FyMH+s3^&j-2l zn*udh52N8MbX_}i4f0%;=Il%f3-mM^?D8_otI|r5&fRZQsel;MLq%<{JC;Yx_~1mw)XI% z-Wg1No`jdKIL?Vx3*sf%&A8{s>3R8r0uMg!?19*a7mcYre^gbs*Vny~H%#F$r{>3_ zW0~EznOr-_zl_lGcmij6NAxV~f`?#(zLSmhah1sH4Y_6o;N#$Axj$~qv)T?WRU^9O z-tBlCd|HDX2EAqIln-G*xk?k|u?hC-LCk}fizwm6FQPkLmg~yf1d8ekJjWbV@Yjst zN{Y61U!cAk0|miUAEzgsOCH!Ufmc%>r^N1q;nhajCPv$qHVZ7sfz-yFKjfJB1xV-&0{T?%zoTLU<(eN)G^w;>46j=e$VNR+^rz*7a$Xl#p?k7aw|sKsI9{%N1&uAY-m zmOD}3k{DerXaAU!*2f47g}25Htq)U5)LTNdr_kvj>0=ai;na_wMl3$*)M+FzDRYY2 z&EzTudqtNl=;35L5Q0%v)}3!`NfT98-;mH;VJ?hmck?rFKP*GXK+KhQOT1h)op(@+ zK*&zrn5>d@&jh=1r2+-Lxl*No&)cqyf3wgQtohV#-ZE4PN+ZB9BP;y84 z&KY`SQMRVY>1E)!61a!eOFmj%;=NjB`UYn)QekFf25WK{vC48YV`Np@o;^khXOXvT zk3Z(A&nMH7nw(aw-nyqr9T>_tT(ywJ09q%CXNz^8OH(`asjHO#<0pE&5<8D{viAaM zLe>7+WrEG7+fguH1ZRu%qYGE4&j9S(HxwK+1mS_k+A)gD!>r)}x)_GreuM+?LxL4o zo#w+Z{kz1w!ntI-{JCfwH#i$m(hzR&@nJSac*d9_(kKT=Ua+<)5(_n8$y7#EMuezl zr;w82IIHsLN1I-Vch@j481!$^2UV7W-XGxZ9u7BycKTSISq=ndPDS z_@-rHgkap2e_X$1kw~^CRW#Wi^h_jo$`4B$P9nNAl}etcb|(or+4QogTZn`N>Tp9F zM3X*&=}0$g%xI+d8JI09J{`nZZ>}z)&lS!2CqN<=67Ly>AYSzb7L_TGJIfjiTjDls zhSvk5^d9sOSr6*}Pu76i!gCX8LVhy%gu7wd$Z9seZ!6s7|Njjm72K9SMat=Mm*P~w z0m&}yA5!&)>RAKf=z;TkCfJcH?+>{`bv;?}HWj3w@yk%`R8Gb|j=RT7w8{QljvxV1 z_I+!Ln*TZ3wDJ{iyRzTow`P3|w=t2rcEtPHI}a%j$<{8+O6z&ylE$fUNjj_15?l$I z{a4x1Kd7JizDH5(O23HUXmaApW^gd^snbY$lte5!;gNQ>%Y)L@&hN_scMtNN|3p_- z(<&MqFq;Y~u_@*US7brgGRQ-@j0us2uU=+B zZeAW@dq|xuL;o4LDf>)7U{7vJcu|As-puH4L)%okbVcCZ*qwDC)Fpe$L1`6ssCWrq z#Dqyd5F(#E3G4rkBt(gv*o-VX&zzJ%+ljvj0ck{4gNso=4S z*t5!xX6!`DE9x069Xumw#uWUHmB!#!ZxfmUk|7U&P3p$bfK0sh*H2n8Z@Yt(hd^?j zZedm3@*I0&t<|?Z%h2UiV4o$y&Q&sX723#4q5oT4i%~?Oy!jE)rUqqwf>jyn&ZO46VbcYV7HVO7)vkwZR%87&yYMww(d?- zB60)>c$>43*vEcIC#xiEa|^5f;5*puuPt%k8%OFj|MNLvZNWCIQ~m>Ff~2H?C9C_b zHzs!Sq_p6f2380=tvy?j%#ox60%^K%JR1D9N!+CRQr_n3Jm-Gt`=anJ$?!~XpgsI^ z@kZh({Z>RIQ<~y4Su-?3uxbsH<8v$V{iCTeCFSRN&ALL-k1_I@-`;OdPkq`U1aAkE(=GCK@B9a zY>80tAq0f6dEa4@A@JwRh>rV@k~)C8mPpn$vLH64u9Jr~W;OrGlsWnR^u^p(U! zQmd-dr0tf`cEHz5 z){p0UxU#L>?+ydu&?Cur8ldTOE%j(G)QVpQfxpTN7z>>bnB*qtXve zm}BUgt4n;5kW7eZ#5+qfkQ=Rz0znU?KWt=Gl`GIJ_Ew@Xo2&-Ck)Kp%#@8u#AJW}k zquYxN!Za*lx`RR9>=EvKl)bTa_!-bEyRa_Mur?)Z6-Dk?3J%_MP_o6LLohD~^P@@A z?#Kv#26pH}XYf2f$(6T(QH`xK@RpCPdPWZ9?Nw)wU#`Y1ZGa7H=o{y;3S-UlvzqRk z8r+$)zoK9jdg6cvIu&-Elqfkr2vNi66Wl$PTooETo-=U7cGYN`J43=FICPq(V25gXLB-pa~{*W_00U1BLWmO=|E5R1JY_uJVc%<$eSq|iO9A(kj z6LQy>Ryw^xSj&m(`am z@~NW_wCDT=^+GzrN6dKfs9i8COueZ{d~TAW4=P5AFhz)Fv{{*DYrSaDg3fbUu^AcD zVa>b{m5p#0?5YNB$4^OxBb&GrFk#M^)|G1LmfXoE;UiGOnNdy>CdkYyWg@nHJ46^2 zV5Pt7g>+(TCP~U>hm7go}+ONTHt($lA71nlKd( zVD88HR8yQeY>yWpoQtxD^5NOg8ZfEe3R=n+VmtVRt2*JF3U0Arpl2;rtsHh$Po4M- zJl7QoEJ`hYVx7J_2&6c?WouvD3ba?c>5&8a_K7KKre2mTove zE&2zKs^SyeOU|!Q3^|Af z1nXN>Jk1m&30;Ro4-cKAh`eN9Tk4#k%W=!~$ey!6h|=3q7-q*AFJZt7cGzB5pgzeB znvEHaNj8nl-^%V9ldR(SNOT9~8TTT>0h(;p(^pH7Lq#7_jS!ZDQwT?bv0AGH%p{tRLGgN%lfRdi?Suo^}28f&CTZne1X z0Ej;9|4*jD@R@i9Y0(TUMM?T+0P^?LHIz3Y|6lE;m~Fodi|j`4(e4XO4=)0@YL2=a z;(Fqr0r4w{?Q_rhk?N1;(gDPNVV}YgPl}bP!y2&QyUl?>ahCys!dTUm&V%wiB;2qn ztzm|Gw|nnfE0fdVwKNX$hk0u%0~Kke&;BcqlfRludgrH4bUFRe4of!P2(Ua4#Vp3h zKaStZU}m8~8tL|*@PCJs6={#2Uretc+#~8%+%Hkxox|Ec;7v8C=5VSY4)++oKZ(3{ z{S^ldqE7w;&sTKBBVSQyL@V_Ehf-Cm3cE`p-jFafIl0kLYyzeVs^Rz;kd2zGZ4a&x!gjN_q<&G(Y$tacj~xp8C`aptLQjsM+o+e0WIjY zFk;M{>LC$1DY8%7{p`oDD`G!(1dkgs^kavhn?V06)gn^jWLW?w(ptW)7$(E+}g6~LWk26RO7|!eZ0W&Ntbya27?{L z4wsJFC9^f6S-@Z7MK$S{(d9U)ie9g?t5!2sYHS)SX;?UG($Ws$(%dEvsJM@Oh%oUxAI7yl zLU`n+;u`NA-4k;alitO%w5jS`gUkU{yo{MF0e*dF*lb0)TpYcd)|~uPBUUwXWnb%Q zIwpgPXgy2yxVB)~C|yIuElaZre&)VA&$;iqzE$l?d}Yi+G6aJhS(_xF(lXGG%~PRw z+NtlE?dRJ0-%S2jL zTd>{6=C=J~Loyi~zn4}==I<~#O1P|H#ccVEJEm7OU&**LiU=X#yyvX=i^bzH40@ks z_@IxelZei{5jl|(y?;l4oGPSaKKzw|jvGCdB88_EN@+=Q%624n%Zqz%>joq@k^E`w zQghKJ7(t=t96EWApo1 zAt4Uje$hZZwU^}tiY0tjr93Hb@9=RBvoR3j@jX@;U`*PrTIE(a)}>sc2Ay`yMeqnw zvdEda%uP&Gs-d&Sv0t)_r`0fmafnUFs3Y3OwlnV+k1_HiSLhh-4?L}DdRt-FF9zax zp@ywfn{wiucpMOZybB?DzD&yGCkXAuU@2%tU`y(zER{lc>du^w^WrSMKq2;bT8j64 zS;sWY60W36gAqvJoM&|ldqVKaD+bFPjh9JMCLb(Xi$&`fnmq6_Gt$=;y_r8NYJ9U} zPom$bCx*>KxctJ$?=5`D;zVp4pF2f+m2i|2+dRUl;DGzMBc$}YXeggHKCEd$P0m^SaS5g-(4RviD2aU zx|pYlJPIbZ;)6Dn6TJiKo%wkqvMXwp+j=Zsghfi72t;p8*F$YGZaI7vNii;OiY#uF z$Rlnk(v-c(|M(jGKL^oMb?nFK-VN&X-+%nZgy{{4N=qdvrGU~Y%S;R9uBk?^dy=e$ zCGM}ad*sk6&GhQ%dSS&>r0iC952fNzIX^@duv#KjyvyERSW&77y<3 z?(PyGxI=J<;O-FIg1b9}-~@M<;7)Lt;I6?f?~k0^+;eW`ojWsc=5_NmMOAlK*Dl$s z_Fh#~&}1-^`gSzytV}4M%9U(mQ1=pB&##i*cbU~VRde$MwQF%^X{(%p<9L{-j@xA7 zZd6&YM+terEU~r-ZmC0DbI%eb_URZiE4I+P!H38zQ!+0NN&vf&?AD_*Q0yKJ6>Rh`gN1_%$ zozS)R>37JnvNPe|IW3G2oS{|!jZ@_$=sQ|iydW^(5vk^OYuv^?d6?F`y)jP%Lf&_( ziNPD>o+O_1pEl6iJy8~e|JPuB2S}DlGy=!4I*DZe*W#CAAa$a9;#copW{Q>E@tTYu z@I|S@yvv=TO9(ab#q_tn1~xGJSD3NQ(q%JxEu4MqjYZ;``?LMK(?jDmV8`AN9+ia`m``y{F5W$wZE^1X}Cds0v*K%jR*^R*$U1ZiN04XuqZ zyP%b4RNzspVsUw%7xaVO%7ow#7f8kEyuqvah*w9;G749+B?0b7ED$zYJ5+42N^P#0 zi~TNoyv!LS`SX^~Rj>Gtc<6z=i{(THGhC;FaFoGL?n=)=-f!A9Lu4;!D2{xlOnnas z?TwF+w&XsMj`EpBLRlEA!aV)Vl|pv*BkRxc<~4iF>O32wez;53rutk|!$KMs93j*C zi%MY}D*ymrU68C!0mq+&s(Oaaqgf*W8Bv2#O_;`2frm+_66%2@t53eUKtQI65qN;a zf)&Hx&#EmzVV0j;TfT(W3E*hj)QNR=aH#=n%-u4td&g+E)_puml1P57 znUf2Nm|DHH_;^t1w)eYMzFt{$En1uNG6`~gY?cbFfV$Bg$(js~G}r;LF0mAOnJoUS zHB;h`hKd&!b)qx8!nhy>&56800OOJbBF(jHe7IF7ZU3h9vq9t<{rX;M!m}8?Ajil# z9KP9xt41QQp&<>LWRn+S(}V7Wa2Ren8%@IKI@x+hF}N-fRXGa0&7u1Y$0yYtAwf2c z{_1T-}A?zkQndvu|#-JjzLi|oU`|`8dsIqaiX4vAhao!e&`XTmx zx?|o>qBNPF!B}_44GDJS zp-z+TW>mq!S4+w65BuadgT#IIO(NroXZOP`KOCAu8sSL(Xl(pqdVFqkYMM)KlkaqJ zeQZSqjBpKmm}T^!J8sWS<#+7YE^y89dm-R8G3J(-9z|lE`eE`j8dL773e~09hC8xV zhzR-kICq7V?6vPTS=}Mv1bldjXd?)}#`;nO^(c-YdzA$&RGu~32#)%0WYX?Sw-Dtf&R4mI>EPFph*D23^`5LY%r$YNKrnf8zfnkVq!ON<@ z|1wVzGJ?YRJ z_(dk8km{`==jR*s3s7W)8ryBkB8i`ubRFHakR2cHDAbTmwW-5pSSee z(m19QpUsUUVJ|Mu+xBq`go>jWlyY2>tlJ?v&*U@YSTd$n`D|TUT+%6QJZZW;U?_eQ zRsaSioq`<&WRg^!iV+c|nOaGAWo%Q!r}a`fFH|Z(%2gCavQZ4LyOb?)mDr3(ApQKU z<2AIff|l#k@9UxkO`gW<3y>eaeI!DMh{z55o*7wOPKm(-<{u;NSzSKy7Pio$Nds^R zS>N@EvjHL9<|>ZSy9iJe)66cbaPr=W>eWIXo;%;_!)MV$VL#=QYa=U? zBrx_IOcR6h6%09-Hk-kBeQ$SdUtzM8?0o@>Td=Wc5VY)wl|Hby0*zT)u)?1>vt6@R zM#c?BTyNVfC`})0?eERzJ2rffeR@uuAoo6v2D9 z)$w<@Lca4BEDDzJPN)}Y!cbMj*~3P@?bB2O=%?;Kz)M6v$y#r{CgUU?Vq}Y!9%5gT zRRZ(#;F9M|qD@e7S+NpEOq=15HgMUjs&A$(!z+m(NEV&az{(OZ9U7-@{5@BW;DvK& zHB(|?nFNtVV$zf+P_~rx`fd9l9J&3uWYmxd@U>THwuO@C=>@wo>FO!zqiI^ihQa1a zQt)Dqvn@qY^-gPgJUC~Q?ei5yoGsL-%7ccQr`sSf6YSo6j<3ic!iP;^d^HkCiKs(` z@{Ti>1b*Y=*>6iMT--?bEC#2}02UTW%^2CxE;d{R+?NLSW3UQq&Qhtf*Jw+>tSp8H zBqTWYwb{sHpk^?i(vfkICRUqfvOZvmO!8Qw+r5;qi${N(mz~Aai2a(^% zi#KN&&avAe&6K{i&~{pI+R`kYfW~xxjuQF_u}*Z{>IE2S05qROmBsxL3ii|Hqqi6E z8B)2)4bvav&?nV*J+Bf7t4Pt_fu{<;$DrWCPPbZ?amTI4prYTsDjKrEH60co*Sf0j z_-OImXoSlsmabpAc37ek=}5^UWBKA0+4v&!KmaD#)rVO}MZllM#InR%hxZ6Xv>(#(c7ZXYEJv+vu>}q?R7FLGjDbU8&Gn$2J#s^;u8>E@d7xn?K zC2AHpQye@f5479Q?5O4%3L&7(V1^qFQ6z_pO z>|K8J{@T@NowxUS?I>`>EO_wKVD#3b8sxob`BOUsEgxuzmxBmNOEY8{2Ob}@V(u43 zSgucIC(cWqAxEu8yQ~VLDQf&!xZYGr8|rBtTgnfjYgfeiILIR%{T`cJ|*c(5)fWpe%tN<%v`xn z@n9%aL`XOwQ3z_1Xv7WQ4iup0sMdCGSQwE6@Pm;OF5V%hZ*B%fT=T>}$8%d2_7MPUx03 zfD$a(DjX@!LHXHSrk`mW`4KLccO6poC6g|R5_AVyUVzwRX$}=Ok@{R|h2jI z?z6#k#J~+RRw!pI7*l#4T|YWu7}|IaSJxaw4hva7E6N*&^gk~cL4DHql9URiw!H9JsZO-*2dgc_QK3g~9j97tnfWW#(&hL!j zq>zeR`N!5+QaCFA>>yPdKQ~tJO485G)6Y|(R{n3k$nYv#KI9!=VEQiDGMZ-Mj8J_j$2SflOja;i2rt3ss9(4Sr+irAMPcw; z;?0viM-~h2FET{xhVXdH@JLeIt-3>V!U$_{xU}W7K=`{!uto95DCDiGg5<=QWBS|m zdKWOIHPDKLcF`Nwbshjd_%;j`JJG^89c&~um51sdZr`YOCn~l0Dv;&q0Vk>8pT{y(0c!ypVDZ z$e6S7DSp5xgZLl+5(UA~_>RhqJQC!@7Yd1nj(H8DlA7smnuTW8!+FgL%Mv0D$vJ&* z6D1QVXQ`JVy=_ZiGeQM1rh2;z>Cpx;}i+U{9YC}5OxKb+dn&S4g&h$;^fgdc< zW6AJ&xWS3Unc76Y#07Ir*S%N$d4X>>DNz9P`ETNPXDr!Z}316m@q zueF(IU8<(ko|R}`9e(06c_avEFJ5DJK2wR!gNWL69#CQ;C3+&PQ(k>P!s18aV7>ZIR+E0UvpI;LS$$<9G`EMU1+KTDLImYj=Rjw;fGDh} zs}EUlS((t4Y}KaMRVU8aFN|vbZ$z0f5a(;_H~Gsa@WG_dkR03LNV&rOFMig9ju>F> zp#dA6*8Ww82FyP&?URB#u_3)b|M-ugBzI;VS;>|W_EcF|g2PdgY0$g<2@ijZkPX?? z$T=6+Zl3s1b#hw#q(MpKom?z*kR58k@mE^Ynr-;!DRJp%qrWhuyWfL`bJ>T`d5^W< z)&qtG-sXS~5_$Y}loK?LT%0&x8GEkQ_kU(aY>6AbO8*$Gd!Z?PjCQz8VUKd6O%|L( zqPu3DT8SUl%JUcX;El#DImG{A^&9O+cIX3bQE89>aH$>cc%Q+QHye3^@;MCm4gVeMa+aoA9vE3cpr^MUnBQ6mop=+ zIbM1}!4~xpO~7)>pCwXm&k4HfJ{}QgYhN6Ct%^JgI49vnl{5n{MOHFMugoj=Xx$p) zVyTWt0PE!+8$gVj7-2V-%2$VDS(!@D&&O7KwT!rFtVp+q7+tSZ;&7l0YvW$2%i}>}Ks3o5TA4M8 z8&?JIq^6LCPVl__0)#Lk$So1^jr{kkNxR6-^)t^k7PKIkEKPuw^M@TVxaWcq6Wro&kgmV zy-Wf!^hSl?bF)-oGyX0<>46@#;R{(wR1EV{LJR zmT56Gs!8ird|&OoBl)1%s18o+D9dK#1RHF90;pS~phqmfBaKm%gw4!qdn!|-(+IN) zS^9G$Hh31r4na1RTI{#%#HUGrfx^<+_6BSm6mE()B-0I)j5jUecm{9EngWpD9D}3^ z33d#_9NeB+R@XKRpi+UO2#bY$bPwJUrdsc0yMrP*TZfZn6t7@*?(4?-)|3D4ikKhv=ysg!3>Qm$eT+WhX!kp zok{u;Q})H=$~rH(xM-+=aJhZ!a0*edSVJU|4hCvDOW-x!0qy>I!uD&*rFR3A8j(t_ zkwP!S1>_A;Yr+~;3DrAuM{hqrsdU!C%q}B6#}TLF-k$sP;{CQC*7m8?S||% z{?HJn4*6F6l>=pshDMwOW!3G)q`D_Ig|dAE{^X?o&-7)I{4$!Wu(vWxr9l zj0Rd>zH5#o2>F%A#L9bKA8x2$c{MLSmBXLN%qxC_#5LW?Smzk&N(7VY7K3Oc6e27o zQO|$y)sM=zaUCga&&TEvI%w(4i=^}HCaxbr=-b3E+4wAQ^qA7?mt`rFp5q=c{<22Y zG)nfN+zqX^W)BH-rXnl@LI!Vl<;P%8TUgGJoW<|v0OsG14UZQew~8Vq!rJ0a-y)og zOy}mjspO8jun`-2z1gH(lHj;myj&$^7Bs2cFXZb2v>Nnhs0ycK*3pRyIvAY|Pe&cuGz*W$^uBnulMW%;N! zDwsA7!%Rh9X1N9#3LUSJ1vJyA>MAyZ3y`^!<=c|=cr6g|Y0^+DEPZT=Evpl(;qBQV z~M#)EP}Uq53!jJiK%ns15sv< zt(o?7eGV4T+_91h2@SESAACL4X8~hU3tV!@u!WXh@AA`K_952#<;Ju440>rSZs=oR z^|Ypc9;_U7rO>eqr+6F;+B=mS$ct?oS=_9jbqQ5_)uskTDVXpva+yoyM_(n$fV6ZZtWY$5(OkWF;2=!{P9ZgfdB^H#^9l53KpY3J)h zFB{2RxwFNV4Kp;Kcaf;tKR9Y8+dF!0lFCY@(!!Q< z2BXP!e6KCVYFnzRx){nqgkr?5x(Fa;l7|qk5YP`tz-WvseE~WTst<`t7oUX$CAK*lenrM@#H4sS`1(qM?gXw+tOJRM8~kU~HuRGi+_fF6Rbz0FRMtU?%a$K&l+cLqgUIn)&7nEiDYW3$$u3*> zK&gfWa;?tK+p$zChdHmTVeNEXXh7;;vC=jAc9Shd0AjR6jw>;m9IUO`iLXU9-$zR~ zg7=Of2v`kMudnFLbv3{)rvw0I_#ov40UIkeD z`-eVm_H56l2uMBEbobFqS9WO$T{_usdMYk9T+4V`GGV$}45FveOmO64o;_oJusrGz_(k z1opkNfP?8iYUw<^%na?s!I%rdglin{K@CmPnsRqS%ldS7)m7)Rnhy{InV2=%A-(Ra{f^Y7mcpxDui$+5v^WBKq zo>RmnATRJ~@%eo>7U&Gm3kJgUOw}X5zG9G=`^E{^L1g9CR4;5;OhhD-QlB}b+pnvi zN_yh2a`P+}>HS1c4ok`4XFX5)cyLadt3(iH#9#%%pt~oh_)hjNq!MmwcmY5JV$fKs3@E4Eh;c8?OB7@LtEUB zkLwxzgyTZ5IAY7;pns7O{B0@D8mQ2;Yi*k=E<_t$(TpiF^=C_uYeFPFz*gjF_vSlsfiytan`jda)~Q8jPVhXDAjJ=a*2sGjn~?N0bOD7u1?m)pj`i1765p)Ddw=29}QABfoR`hQTOX(25BtR>-?)%^ZhafV%6>f1xxQ*Hg$8c^RB*o0vEF$ZUGC9jpQFI!y0NRu zbxnc<#wn>9R<>4t_J_+`=ff8ueG}2MwZ-;8JydX}u%^d8HFe3wrH>1i9O>}h$4Vp(+R0?E3B$4=qf3(%$?CX! z>+<0bZ<*j66UY&Bm!~Hv$cm7ayH?9B6|~CG`y=WX^dhCD9FW-#&-GJ}xTTR$Ok+mb z6=4#p%U(ar{+yGmJp`hG&pXiRk@*iv^c>>k$od7+`DSV(dD-6&#ESq{_wW5|YCYQo z`zg=vKLtVJXgZ6^Ej68pG%c&T=v>vX*VmHN+Npx9ZNe|fbV-oB)a-4}^>(1#yGQ&2 zpayOxTrSn7631$a3Xe^csC zZ30&g^3tr!mRCbX2ih71Otv7HNl>_D1lEm(BQRyp+^=e>N{?}5nUpO|vNuFAjj#r@ zzwb)A;np-Q?vHh#Wd}zYh;$PsoH#VP6Q{*@I%qgXg0;4lQh_TGVH1DoemDdHLFTa> zf2kU;Db6)YB&5PC)(G5JP^zD0!tly$lXn`V9hQQ(DsYca>T zPV4r}0)f-kmpd3q-A^jiB9M7#wC|yPsRYvv6%n^dTr7~ikT5&VID;>!UZzic4t7Aw zS;q-AKr2h3@E1`w?LQO`l_!!^cy|-Hp4?+9`7$R)Yg?QL`|-q?v8CX7q&2+^ld}5k zuV|yMpuunLr0$7t8qoIX-ee=SEN`)lt2}2h2#y2Kydi<%G;j<<|<{fZuqYJQZ@=$Cj2k` zKsSP0QWO<(aCH2K?LvEY@wsJ;0$KHYMK^5{J$n3q00kJww*p>@=@FJW>vCf>@)?q8 zN%^!i;MNvIy2K`-3h@uB2%?m;0c81+7GsMjX+*EU6X6!B3R%xv$x@N~4oqe0HXG zyIzWMTuE?*RBmP^2hnUN3JKHNz~Mar)n~(_v#IzPr&}krcBNkD+5^}}xC#Z0I-8I& zb>lVh`jIp^9GCskK7h^lv+?{Om@XOnk(M$#;Ls{_46YSw{RQCs8=B)G46cbbD@Y6r zig&P%De-sedw*>>A>=|Nk+jZ(6qY|!I-pO#rKQ45_ERC84rbGbQv(F1s$g6H(SUNQ z34E?8A8sB2xL3I+k`xcH3q?t$RV++&dKvBv6_MqVP)~7dox&2`NU|RLs$D%cgt>%s z1GAx)4YBJ{GcRss7Bp;p`;xDn=VZy-djCH<^aUEEW+GyMW~_}U8|IL?1NzRbtGC;h zG)bZbm_k1b6#fgz0V14Lh%(t#Xuh}ZH^dlMrA)%U;Pscv<(I@>M-v;(&&ZVP26jt9 zH6pB9(MEy@4N!@%LDi`?`lC{HY<%?Rnlr!-J z{aAi(FY+;hkTlZzcvy&3%KJOe^2Da!@*^V+JVApvFp&RjxRAQf2?<`zUavMf@b!d#|lzOQ4@y7AD26hOn(8gTngR?mXeb|yf8(8p^>)TSveJQ(XvFo#KJSYMF zSVE)>Jgb=2y|Or=pPwI+h#Cx-F4f~}X4u6eJM>C0c)X<-CMwL4fzXU1+_7{!!wk&QW7w;i>7t$JX^UX}|UeEyPG?>7g(}(JK^NXEyLX-$1 z`2D4dm7GZV3d4pSoh{wWyu7|(7}^zrqG`vy%;by?Tj5P)}Om*%KX^6m) zG<}WA*|35!iG{?Z4L*ref{WJ7-Z)ZY0D4d-?>V>4;Ulk|VH=H)UPh*=SdC>GlaZ6K z3Hvw4fX-t5F)cCHhj2mY>m9=@c;L zUH0TI(qzAA3HfJKlI&_>7Vz9w}Tt!Ar&+A2N#enlP1qjFK#y5~H35UgFQc%4} zFW;-$YjpGnO+M!g4sM<-Eh@DwGqhsg!LwirQxDe|)}Jvzug0uR?kVY|GiLf9i!`&g zE36lzLpq_kD-m4h{|o)6IM-5INqg@SJz$w(+h*NK(_r1mX{jS9=_v>d({%ak?jQfO zaQy^{i}cPHNl+fJC)HqM|E4cLWN?`y<^U?5Gvr+1erVW}pHW!v)9^p`a)3G#U+Pt5 zN*FeBz|#7!1YSJ!9RLVFw*I@3cfqllXd>KPG|4?>?5a345edD68%B0j4f9%-|8YdR zUR{&}iu{Qy!L6$1t6amwg2twF?GYrG)FYT zmx&lWoq3}{tlY8!oD@gYHqx9H@Yr2}6>BB`ErGbxOe<kLQx0(4;j*g%*82?zuP7z7*)1Q-ki z^xNLWz#vG-h)ke_M2s&`1YXJ$Grs~8)U#8tW%0@Swr?;fFfc!0v&Je-@loPf?@_XD zn`|<>rK?f=4N8z*AcI0{E=D}7E<7Zw$-8Kc(CI~?ks2?T0b%z_u7p238Q{pBl z;AWvPNP?>)b%$M|&dF%TNl=g@G2lif$Rx9Cf|{NTdRK*Q`3pScF5I4@x@jTgOnmZ$ zmZW~?PoJwSp7;f5n6uwdM&}x^6juo4bEmJqgAC*+EmFX1zqnE7aPh@Z=;;BThT~VY|fK=?=(?a-j@vrOP7Rh z6=Z#0t9K5@#-hJJM6S+Rn16TNIy3kvlyy0Bc$p^+O6(@Y6P40vIhInE371ITT4-#R zqLdjQq&AwCNn(lE&+0R5=3Yi_9ONvSGz7Ip>E^6T;djo>iOo0RhZ!8U)?D+3SYQk; zem4_Djd&Z^9^?MG-oSayXeBAeZS}c#n3$E(snjMx z7vT4U#5rYh4yt~*d?=GBWe?`R48rp!A26)Tshw<(i`a{)+poXU|Ch%reFZAiz~W8( z%GF^FohY>fI)zH=^W}T=;SdkM8J^qV%Dtl!r^L$7I#J*5(Td8ioY4g)*FNTtcI)$I z9}PGcQ}+Pn4LV1N;W;ko%<<3Wp(@ooNy%ayWzOuAAv=N|e*);_??$n@s}*BxqX z*7$oeGatzz+k9lz4V2BfbX4&5Oeke`;2o;G2-*O^O^Lj^nL>bcEaIG7EPg2$G_q~1*fAng&$I!(I7A)~7K zzXWbo`-klRMT%RW^+lu6sr)S#13Hg$u84iDE=Yn$d{PL8_9DNvarr0WHu=6!sP1rT z)2>DQ3?aV1KSH#dL?M26&^*=DXwadDiDZwP{w{EJ^dF|{(k{d60_!JyZgd?KbUv4v z^(>lcp=?t*No_pPIN=bfosw;nl&gq|Wfl`<)2Q(zaC66)5<1PjyB%W-4QX$ia}p9g@jbmJ$~g*B^Z2 zGXZWMU8*m#YV~7mS)qATIo8wAuxBymiFmelxOy0qF>OzBHM;)r_uv7ZQ9B*W&p&@j z`88PeHn|*Ix9T&2m1KB$5PkRe8oos?rz<{9ld@+Cuk(m~OiF$bE*C#qNsO7%JRo4# z0ZX&N3=2Z#d~fZv_64X^_pqg}?4R-mY~sNU&J_3Dx(^V;zX&~6sStb^Yj*#*O(^JO{A zWh>G3Hb02gxaS@(NlE<-pJm;#_onxTIxRn`9Zt{=N|4&cc)McuqC}IRQt_}CSPwxv zI`@#L!S;~#QU(pDd3GbGcaq#qR1lVv=|xW2`4=D^i&LDe%f%jmK-E5rs%Xr~-j!-K zjpezyWxSqb7{Zs3!b53ml2kI9X(X$62)LV_NtY!D%aa_%wTmLnpdo(mTK*w9!$Dp; zUy&+)(1|3iA8efptFZ*kW+r4%eHB%L5`S^1LFC%Lobx*4eVM zTZ8QsUJj>ZBWNqJrC$Z{yi_A+!rLVK>7&47l%IR`#1|ln{HKpHCnmX1JsqVfyZ7pQ z{M8dSrEk-|07W+-Uz_Camt0uny^X5(|Nri;DvO&0a%x1l1}I5X6@-#W6o zO^IWArAh#MhWI5(Lw;)YRD}AzXvrlp*S)ZU*;GJx*jyAEUv{cHOvp`15NlJ+x~L|+ zc`4ioYxu+-(HU_)0WxL${_vEyzJ07G?Ac=wXMx~4`GJJhnxsyDxWz{=WYX5b2_EOX zT&KjlK3TwyR-B~o-Q#vAudr>-Iisje3uhY^b07O|hR7wi>}k#=++i++%T#9X_|;iT zOg)*p!`)`9I8rb#-=m(z-4jT7k;tsx8hObPm5-p^^6)pGaeCcJ*?rYC+lUz8|;SPnwYc+mK-2?%$lOx|)}|ChUv z)_QqWVow^$8fSEOCF^cAzm@9sjOICom!=~xhQqo38*MLSsUAREhbbpbq8By(&7l=@ zi(X>hBHs4-HJMha>IX9YTWVY{P;@e_V?O)f1aJIYfbIzuA&wFMmjUldT>zTlMgjr` z0euDn4mcq|fdOy^81P>akdVO;QBawf<%#qN8D9vzBvv31d}Wu#QVDMB8`E(rUq}@k z02vzlzpB*8YGHq_N^n0dTEoq>@6WEtTo^Q%O+X#&@ovs)zl2e0HT+~I@(HlU(>)EX zraucAYZrtrcVc@$EsN<47Z+UtVexKeVu`ZXYi|s4;l1sZilAN(0+A*i;tE*OjRS&O z&}EI9OemGT_l?8iBL{71t~-%I4Nq0T6Yls7Q7VY`v!`^n$k)N@a*gULCB-Vr#njUY z=7Y6H8dar#H==Ofg=$P^m9EY?o;GLcKk<_D3?sgQ5xUu}tog^~QzXJ?1*|pwH*1|t zC`7fXZk-fGVJ1l`54o61m)y%p7f>Z~MbU59mq858n_|YLsnZ7OK{YGy_Fgj{@EcBu z@B7Qocny@pqbP$EK_WWJ6OS2Y_jHMTFwaLM-1YMv$Ea~H@=*j$Ui?HB^zkoe>R9}KnX$lrL2p&L6PMQ zhiA(O(<+bxnufzo9}rBFr=?}37BY>E++$-=^%{)EoCq&+l7#<| z)gqXt6();&UsN2zo*$*}me0yflAjLwnt<}e`S3jmrhN!?uyg|NbNLjZHc=R}S7(Su z<3dKjLP0N*vOAdbJyBaS6a&ar=NCCXjz{B?Dr$-+`BrT1r`vS$8al~X5exO0SuXNN zgP5$9g2=TEa-*Eep_ma6j&!Y+@|;v1-H@EaE-{cxT*ck0@Z74MY#FpCL_Yneg2`?H z_-#Q0H79LD^%rp*Fh|dJFJ+iH+6b+S(N@hZU%3&07kFN2w}HIaS1hH&rkyoPJ*?JF zw5S?+tzI_7HgDYg_oB%A=*OtnJ&PJL&C4hjlJB~o1kwVoGTs(iIEqamLfB)Mz)QMI zx$zXfOn;4Y^tF(!C$K0~Ck!lnWmqOVGX8x~0Y1~9X15U57z?l6vP#FD*_h%19O#+? z_AD}L9q_A+>-pEKrk=7%B@o(xNnQ+n6a3^?G1VMi3TfuB>+3O1Llu~t6q((*<$Kx` z-OJt|Hrw#ZmHNV!*}M#c8!YS1;C-w&)$7RIWSrGO4ZX)!{upq7QB<)Z{oRuVgDFwytdVTh9_191n<|o&9)GbWK-4YT%Ew= zsvC}G2T`haK1GBTv^xC#185idB+`Q7PsMlNDe3Mc0&ah!S&=VtAXR(*y;XgCJOQcBFs#SuW=ph_4eAB9{`Site~J8lZ;&bcu19cq38mcoR*$r= z;`IUWG=OWmD%U6cxJVla zOB5{*mbUzH0mEbXr4Vmwt?^^wKa?Vg--)GV`yyA)9DJ)ym$=}|3b2%O=_Ryc+l%Pe z-~_IpOPb5RK3R7!%|Fa*$gW~^Zuvd64jkuaYJCUtsF=!KzM#V-CMGEZKOr~eVTrUG zJ7Y^fAt`B&4WCnz)A;bJXZ!_8rv`j6R5~rWZAD6FrwPn|CJ1YKb=opeu=$ERpSU+e z@ZWn2BoJ#8`{x~XXJSM0kd%y5JgFz>i9L2*5)d-@i^}Uv?x98veJ}Yn|GPzg-_W~d z{#ajZZ;6YTHPtEN3Do$XN3`DSU`bH-Y?G-Qy>40-8C%=r%d-u+J)1$H8mXLU!o7oa z4WQjA338L({(zwDAguDsNL%Y7TKi+aEx+J#7^&W3K<~e&T{TKOtoBT_sT#RbZyVzH z`%$xoR*#ywJkazW8>$0PVEI)==URHS`4t3vZez1}sU^ z*qIn7RwESc z#&mIms|R-}yjjOKOfkDJ%WI(mC(K2C6FrV8`aS?I9?JE~mtxff*(975T%1}+S$tfa zS}>_8mkK2BFz@g^umWm)V4;tbQh>Muw2(5PPb@@+q73&9KKTSM8F6T<0S5x9A9%kQ zycv<8sU5w22zq;75iB>(v{)e7OBEBDstbS?`7p=p?%(7u?BCNj!BN8D)@zL~0kmJC z0UYbUFP)w|1Bcofg(?|caunxb)X^$A%w&vN4XYkV+l8(d#@Lgr|BnbnmZr(J8=PnU zy|1qS}JO z;FYE}Yg^kVolA_@?3kwwsuQ9R{^OtcxroG zzKq1lJ=+85Z{G@C^W}AS$|NU=_(=e*)CQI;Z(x`XDAT6M>l6YyAh6v9XQ+mY5wDQL% z6j>UnjNB=GBi-h_$0oq`;v(Hp-h<8)TB4;ul$VKY1=@-h7HUN91ua4r=^yP=Kf{%} z@;?eE0xe7ID=poGJKmx8#ZDr1k30ia(uOAOh9|Tj%xSQ+_UkZN-3C2WiVvNXgL+^e zTDse|#vtGxs_!uk6`Odzi$sI`vpPCw6^yIl?)jTW5bAGfILXFay{BPb+PqxdEEyw6 zx}{+pp?o@EoR^F`uBN-^kIgWLUs~PFG6x#wxTRrUt-qzz@Ii)qk{o7{%vgy8P(5*lbExCaXkK?98h zcWJD#0KtR1I|TQjNdx&i=R4orGk4}Y^WSyXy8p~wtD&pkx9Y9#s%`b`UA0p<%*o#W zLSVpB2c|oJ*R>1i9Mqtmw_EgPhdodr*j0T^QXa3i-E_A@0fS4uFq69r&&xzv)&B1V zMk%*sBl!=lA}d|o;KQ)9zcRyY>pnZKe>69EH$r+>3Gp%|G3*?e#7&L;QXh{=ISqL* zCE3D$AB?gO;;5!M&PJkp{Z*~Lr4UCex4jVgG#lY#BCcc@Q_|}OwU9f%mPaceN}rb<`g%Hv2L`^B54XvILoA9844>M`hRik2sIuX4)Tw;*PFPxr52W zRkk*SS5{4MsAW9K>~;uuWW3GAWCf=DLU6}Aym=@F&qWYNX~2r#-MRDaYrmY+9E6SJ z9@O?NC1uI7wH^vHyVs==tSS*xyoR>`Mrnxu^s|E>KS8kkNU#;ad+T-cdgy5j%h^xq z3=WA|Z64CaE1JjR2jzPOr5`FTj%|WJSUdBIlJ>zi7^@RO6H|lknpqOPA8qXJ>!$P! zZhsO(5VfY)%+8;#h*7<8)6E{jvJBMus{kl#72bFKgll2&b^Yy_EG+G<;7WaB$4*w< zE&Dg;mN#?(!$K6N|NiE;Wq);os!92jx&NKYcNZ@(0IN36hSlN@`eqYc#WZiTwzhfC z>GG)p$lg!c3;ttR3o<%jLYZ7{_V}#yFMAFbx-XxnPju0`jeynCQ3Q@=g=xLioWtrn zKp%?|ev8t<(_9sYfQ;{acTaLk7s2iZ8bd?zy4r8+3Q|)kD@%85vbT3S++NeUMlDH46lPXG?;VOGuAf$W%G{Xn&aVe$$S)>#e2{;q z*y80B&NoV&oFpmvKh>lrrj$Hqu&@Y4eiUJ6F9tAO{p9(n+dj`G+0o*%b6$83)|CtYdQ-rv&Pw$2PtKj%r6f54Jx zqE0dCa24Hg<QxqevaoZ{UDI=iDVm4I{Vur(=WKol{pgtvdJ&;TdgR5ouRRS+)0U*#cwOt3zp<&cSO zw$A#|w{NC}&>C;3gRo8){nhZ7tYTEJL6EsoU||2Em}9b1@(c5{=}}apdRqk!iA9?o zlTh)Dq2b{?uiGsh(q{5ae6(w~8ab4BD%u`HGr5u z%2E&2?bZJAqPYTCz*7zhxhbIFOd zr;4AFWn zL2BlEMkIg}^b9IM*VWJ5rv}vL@)n?Y%9u1bK7r@zksd*|${JqddcY~`PtDUW#C9A*I}dT$D=<1tEaii3o1K=Hg<1){eJH|56N5Y_7r5xS@5>&P|_ z0rtj3{ih;VkKZ(eNK zq3-JuCTZUUZJH0jMZ6<93{mb4DdSvrG^N6_7V3wS11LOcRaxK}+J1vBp#=4<4D6*+ zs4Nyu5c$W_{BBfw(87$^-lT;|+yZTP?j;jXmUPD(%V@2E-qYEzn*eQ-_;b}XEm=YG zM(M!oSjwTFWZ44}Dhqa^UhlLMO4M2J6+<>#dTWUyE0ys3YG(*!1pBY%uCT$BG6GEo z9=I?aP78e|-*u`iW*V4vB~5j~CdB2dk*-LBs?`*<34V;!n}S*s&biYlPCds>xnBrA zYpg>3EEGX@%KTSJ*cGE{VQtUWaLc>=x*&_2L}{W{g)gkyOJaM*-ucCH?gw(PPE%5@ zC4On;Ng;t;mqTkuh9jAuvJ^92#dJ45jq`q-uE+Jc($}-{bkVy!iwMci#;xi4Jz-S7 zR2$IJga;Aq^%Y5l;17 z??xBoZ3ECKuio*vU1*8wVUEUsgc*c)0@C#ltQT^vZeo>sk#GqWY)F|;xbo89FiyIOdta` z_P79Urxn(j$yTUIBs5%rj4p6)er;Y@|>h&4Q z{L|NZUY5XZt5yuD3xH4r9tE1v0`>INCv5u_keDJ#$F1hzEi;_ky4xt~3F*EsYn{pL zUDD|Mra}#+-hPe929UKu{Yng*^1ap_gD_|S@61;dr6cYSTN%F+W;A8zD23IjtQyKp;zeBjxs;UHZ zE@oC($5Zk^)vLwmUUg-Ap|{p4Cw(pW_ry(wW<2fsGDCst3K@^C*iP%1Db8V55?Zz; zZKbHUqacV?Jvh+~wN8L#M3#ddVYG%E@N@as?&a6*-?(O9Ep*42r-ZWAKzz0Q1gI7u zmir6{qNawP0xeE-(Wl{{I3HxfL^cu6w6K@;-njY8kPs>fZqm#z!YoF>_hs_H>;!1%a?|CKG1Y`eaqyeL_oTrQFH2jAt1E7zDcwzXJ?B?<+=#e4LZWr{h@2o z&{VW=#r7;_oNRz>p_}}Rv3#$Hwv+!$KJ_)D+4`F-vhcaP&4N!wlq650)qDIffNd3C z^npFqHF7W8vMm=ZsqEP&Ykk)$?(k2;zPRd^Jk(bun&O}1VtAvP+-mT>s;kc|Dqw-RkV-cW2<3(d@3upKNLjN^RU&Zgzu-acVaa1oM3K=y)uX>H4a3-#IdIld>q#W z0#CYPZV~meMd}e&vwb8Q_FnQx4RE|{LEyo?1*>_5*xId-;2);O4O65EvO7r%G$&IU z|4h{BPc(zGwni@E?BnI&>AA*k+$>T#zcjxLSYj>Tp`S>(MD}1A$0Q`0ee&_z$ZlP* zOJhZ|oD1_L)`$oT{{^p}d&Py7E8MxoAuXE|Zxc{V)PYd3v@@P(`F0KBH_NJ~uJh4* zJ-V(->hWgs3*KD(&ETQY;FA*~1GA~7DSQ#4o9rdsi@f$wc)%2Ug^N$*LEGqCV5b`R z+jQV3{=wo0aff7;pZ@cVnO8rmRRj+%jls^-yDfa{CI6pSt9mE%P5qjtS>v)FSEi`n z_1Hr1rPir|Py;T1a;D_v|E~*y3le=c@5WF&SZmv>X_g#r!NT50llIqcIew%%BPp;n zRS@#1_3nh|-2eE3>7v|SWuB9pylJ$q1ph27dCiqi=f5dQ{(C;)Q|Q{w9lez5!Rri5 zm_h#>H0Eul2zyBMF%1vlnK~M}ni6|>y_#oo6djX2#ZyrP6{c_3^gNL(q0lk3#|6MX z6?(!JNmAwYUr;fAZ)Z;kbZwKXJj0qC`ROj=PsE-63t=!vaYhZtSr1pj^9)5yh7qW- z8;>ucPJQtUL1<(6n~Q^-kgiLR?6|`P4VT$c@SLw4k5t0d+bj2*fG-_-2KF2glXBB{ z2_LIL{aXW9VPxSNCCjAk50rcl%b%ZJ6J`|2fdTRll70jCdm#gqwKFeImH8H-PI>$1t&|nz%WzD>Z)L$=Va&XyvJbQA&D$ zY&s9s(U6m02y&~q-oFsC&IML|?l}%*6;zA^Y4)X_4op(B-1xQu6f@Vns}5wolYicp z@L1z(gRlag`QjqWhNwvz-K5>blg9#G54A^QZJ4{k{olHaoiZ=V^x*3R8_ zlp&rwT$!QVoh$yxQI8%wfyIQ+;n8bc)t=&P4|IPM;G+A9EWbt1o;99O4Bg7P9yn8` znmV~Cpg_UI^9w=eCdw6u!(WFi*@?3~!R&J0f~ZM4KIt}31MC1c-I6TJMV<`vQG zc$w1Vh!@QSQHDI^jxt$=<^zhw5G&XGIy8pgztiAF>4p?WVZYt6;$-Bcx~a}<8H2>4 zk}6*T{5rE|(7I{oJor}_6Jk^Q8R+780BgzF zh*dhG;U~gvQk9Q{#IHQ$XmwJ)@)NblmF-tup*~evHhHlM_{r)Yp!1b)Tdtu+RCq<(a!r4D#H^OmawQPo0gp*+r6Xr z+Afrobqm^Fxn!CeUOociWTm6GMx}P@Uk9K}&E5|UT3{bxIfbdag@a-%eC>Z%t{hrs zBCm8!64ooSu;+?Pwd;rWLlzEn3vI|FqoAdQ4i?ZuQw~d2)RYf7tV=ib4&;@w&)q9{ z8k(=!Klai>p`)%Yp7i43zYq$~-EN)W(JfB)L-$0MNhPnA&cV}#+Z2eg+c_TgdQTxVv2vuc$Ynp0Sv$(`U@Wz2+U$^JqJad=TAU-s%Ps^ija8A?i9o+7$p zgb*6c@mv$e{@HKlba$DVKoy7e9Bl3GDYTU5B~*@&s7cOTh_87Xwu!KASelPhsq#28 z;I@?Syy&_}n&|mV-UIvoP~lTY08q1vM6OXUW(MLF>m-=Ft2xQ1TUg{uNV>r;+;FHN zxP(+z)um)Gt$h)u{+SOP9;6`kK8@0qxhQ^bnjG3AXI!OdfAZB0n-W_}<%8886?*Ve zW^%&T>9ZvwtJ?fss$U55{m>TY+Kvz^Boyf- zT7?oFid<3svYPtS!U=YlSv-I;H(ssry)8zip`ecwiCPPp8P{yT5Mo){kUlm_XFyD=H7AC-pNF^3KKdxz1RrEXwzVK z-E+|fNJ=c*28&~OHQj5lcN>%Yq_Drt;ITT<*yHli@Vh7Ex-k@0bjZzuW@DTM;_rFD zuX>jHVIo!+N#3#%SDBd1&7IPB3L@;qi!oUV`;c3*I ziS4+BOr468oEulrq#QWj>anzYJHY8kCGq>J$$ox|LBA0X}g4=%=1DWfg2B{MOc|+hLYYcc-WHj+&Fjqu){<9x}z0 z>|)QpmYmh*nR;^4JFe7dHvpGw6~T_C3JXOl0?Z1hYxR9k$SkLGxS}tdD=^N zWz$<@hzFqOx$vI3@1f+!#byotv9bI@^Q5s|HG>(vTedi&!!WR;v0a7Htb_08SQQI*Ji88W3H@m~)*p$Pv!Q zOdJR@bgwg3ds!cON!0jtsL_O^Pi#uy>+Y+Zse}-*?MJt^G&SJ7r<+R?x#L8#nx?T` z4XiTJ52y54qTS2+TQg94z)|C}yo-6r&EuA=-cJcp=cfNpY=d)Wv9GOLyg>y~-QoPvK*<8@g1D z;JAipG#6a&+fp^z{fBy6RlbO;WI%k3=D5k&yca3^I$$3B7#_TeOGDA|`RQ-Faf5ZY zAqP_DIMGyv3~@YLz#OdFMp#(qqn@bNb}7}iL-25PHk@1_!eQ{YFd^Nr9%U7s?3O!3 zUX$1pDf8VIr+Xj?y-7S5$rm=Bge%ln&9Rn`e@wtX4avaV-$T=S;^rST=`HTPDmTvr zPY9i7vXCCs;6m_w2xc^GQ>pDX>8=saXfCrS#%cCH(}+63VUvgl%+o$%yA7t()s2YK z!oPN%mm?VP=9vNa%J)tt;~!ju^WsY1J;synv)#V^552HeyI8Gj4RmP5U4i&tv_^dX zNpr>4Mrcs*?o*pr2zCCSmQLv(#`x)b?^T3JJpxIRMwb3F17dkrytv!ThO30;jUU?{ za12ypU(b`dUJmg;S!%34y=ZkC-vF)sLbwkYtzmDnn5&g^g?5)@h?opd|Ex9 zGUCH3jFgf^4CGO4(u5yx==ZE5`p^FPc=48jZjo9UFaQJA(SiAz7(%r zeOd$eICraAV_VG3d>>}7Z_CBE+qy3M52-Kry(xJ3K3}_WsWAv=?AA#w-d5zrXtT%q zR?tzGe;p{V<2kJSKl`EN1tSt^5U!>lbeUKM&}dw86?l~Ln6RoV7$r8t9CC;kOI$7S z8^BFCHx3Hfz9*sCh|&5xdQdxsWeLI4Z;r6CW&p;h;K1@v{YP4I3q7y!*NZCF*oXsF zB0~F(3vt6(kuZAP{Erx3;^zT6B^R%&YP!@jS2kbc3M`wwt;T+T0z!|P2>U)Z-}>_^ zwGqPr3ghoQeS71C75!*j*~=laq-V%R&`_?>L4|4*x;ER`x!U^AQOA0#1GzU2wX$=N zOBVRuE2{kgMrCg<8!d#X@X=KMXo&PHLu z3)6F)@Q!YU1Oa#`IE4=T;_HnCrF2|=fHU(~W{ zFecsctoEvA=b(-^`WvqJ9{M;1+%uH7x)dnWS^6^)Dnz=)^Ujr{YFd`Q&$?OfeYZ%H zH%onrD=CLjEWv%70w0yYu!R26?TS zJPQc%nMgAbn2+h3cMPZp5jIfSe)9fU?;50{voVp{uKi)V%^F~Z(bW^}q(+hTr<8PG zSnPMrcOdHnt)LODiga1je3-1lrav$~=~_Ie23DFE7&0Sux0QIabKM7)3&_v~jtL_`u4jg9=5c=@mK-yY# znZhoe*H2M{5@!{=L||5!F#cu45@Bzp5;x91NqHBY*}{9A0~VHEW}OHN>>70jbnXbg zEm{Nr6L^^_U$!F~V-L04gzb2X<3+Gwd%}K9;UAbs-~`)(`tuo<=Q#EDA7L}rUum4n zWCYewbE%YAXU3mW*$*>;sv)m9{Xz|SlFDv^bE`TXfzF$G+e{A~=2Rx9mxD+=L}6Y| z=bC$tPex>BiK_INga_|*=Ox#BbOy)jobx#RT*<4M`ZPxc%+6GgHEiRN{t9*csiDZW zJtW#Zyvm$JD#k|IGh5^RCcIRKVFb4TxYm+h3&1&42*}=ewA*m0K3K!F?Vhq~nbImF z1tZ%4D_Vcf&9d2W7J22e^IV|6n*%tv5tHK3y*Ug+%!zSC8tSI+(kFGC6}xkPtGFE< zO=2!}95s$zciOOI1tBPmrD{xB*P}Z4Zt?sxvPFJ#XR49*8(->O*oSWFy-*Lyy+toS zrdc&QKvxq}8&0%>v8#scG0wDwU*)2gVS($1ncIGxh4DUP2aLFq08NTn^_=lTQnu_Z zg7tS4SzfdKgxQl9812VFGK4hmM;ol-F~S;J_DU4}D4ndT7rNRMyXui%_!%kNj#5lS zEIv;XLRV!4>WgIRwUqdy1Bjl;yq7fmg#de(y8p$^@=bwk zT=2>JB@ztRfE0 zRAW0UNYxGXH(%mcuq|~>ZOh2pT8&GJ5Ub)5Ret+20QtIy)!M4@Y-k{HLa#VA_JnoD z$zgd4(~SeVEF-_KDIf&4Q(%@bEqu&PN?$!%fE(nr747z05LBG#V%SIr@9HZ%N7#eF$cEdR zyidALAioeeSq0=m3=*>M_A|h<5^cMfs#kX?e)Eh{gyxXx%(%MK8@2Kzg1bJ##7zKy zaq@jTe{2`K+}J(v(-tItyu5pzwBaU6@_5E|po{Pjr|2yFIEJMtQIlAThDr);G^7YD}E?V7J$-fnG2uDr5}%yX$C{;g_Mvg+0gtAX^``+(&1^NCH@ zmf_=xW`lq^$5hZHe_qopNFQ0AijGVfCVjXU;;Tnm4w==M7ZMu4_p*%P`_GM0in51g z&c-{-wPPi)I@l8fgC61N3l<6h@YFFkg3nSJ;1P|i0n|gba~yr%I1)dE0~iFVBza=I z@%%CFrD%12MC437pA@2u2xV{#xvBe_-*s*``l&&DP)u=PalvKx;_9U@CisPXEQBO@jI%|hp?6Lv0^j^a?nm$9p3pA5 zZrinbZ_Xo?NYCc06nzQ;7@6}HWIrPiheo$?)x&HiG~WW-JC~y`&Kv;~ky)Sf$-DtU zM?75~DCmuiFfcGRY@^1broJPNso;(n|3@DbDs5AO|D~iWoz8QK+;P<;vT}PloR7$2 zqOo_|XPd^??fjD>o#$MC>$ya#KG)(B>7pTs&r+i&Jc-+G*)DiY$3>E^o5Byvz~-e_ z11P+zBU(^E0NsG(eWlnmma=tm9H2rox0~BmfQ8~Y#iUH=#)!usqoL#g6JeQFx~gPD z@v9O*cUIWfvPjo@9q+b3akNj2O^*$kmq!NkIKt^4$`cMZIclM9x}^f+!U)?sZy$}{ zk-q;oC2jsAG&tedEwao=XV18$bNubj=%e39PUp*&7Pp|Bo#D*;TTbKky_UDT@N^Bs z9|eD${x_^O7?(ZwjdTSPf3k+@xYHL{SG_>`eZBY8`hbQP1eiISW(q0(7zz|bjv#VA zV(sC%Y%7vFTZFFoAKqL1tz~|a4!I|T4s-hXzCsx`zXGtVB3JPtv8fi(058m@I|nhi zSK{aQ2T71V5n~;&yt+knd-dk7_=cT!!DE&>Zj6L^Idt}-PSW_Nv2Y6MYmSCvs+)tE zDzdT}sqzrp=*A>;>fXD&6ya$uIpm&pf^EjZ7hA-$$S19-&Fpk7@C2xuyD1FG(MzAA zld-$^VwGXwsZ%=msmE5{$;I8V{F{XWH^BjV*XZ(7145$$Y6AQbO&A_=kO;m{dxnoW z@EDz`5X`Rg(oE2T$O&>$;{)DDI|+i zcX4^O9k{P$?rpHNf28c%y8&?xJ=?O*pZ-xXSQ_dM6}|^kJJw9zg;Hr??rl5 zUl*7=Z?22%i*Nr^cR~VBI;iXZu;bmJ#bg$hAEe_-$6*fD8`v0B)>Y0K=sK`h@Tcj3hgnB$Qf=%0 z&)w+A;dxXX!;~29*>Ck7Uvc>doX`U_n26--=NPi zd6 z9iJO9I=eGFE0ubV++*Y6xC^82l~X%%t{QYi{~BO{!SqLL1ZYPHWB0|&|6?x%%XFq7 z@-tmkvyj2za|5A3EGDil>zzCHtWH#__o4>X{87UMP-YF_R_ecku*4tam(O-(#`t~q z4qoYPdEP>3d1V%|4=ev%*m;EBg3hl$F-53zs^R1s=TJ$uhzbY4_4Yvec7BLDxH*GK zfhW5(PoRg_N`luapz`Wp88I||xkPL+=0R^8!^G!7UHV;M+>l1vvITAxzcST{QfG_a z0Ik0=0_D>?Ik-bE?wZ3mKTVkn1IN$7E!lGs*$|a=tKxT@-Bjf;pCjC~UG|&<1&C#kR!D zR@rg9h+Ty|&Y-Usu_)Z>+)*}Gst-xzvwRzG`NOXSB6np*dLY@Ig{|Kf>9<3zuk(NE z{Eh~#)LVeaqM2yT&fOSQ?gNd4q~08 zJG`zN4un0!m>ft7hxYgc)2i(+Huer_j?SuYxDt0Ss{U9go#8e+xQKEIRV0qND(3I6 zMhYv2Iv7ox9B5;qpcAhMNR(X)>{AM>*9E0mF#hg^JbJHvjJ(%g?XaThTsc1Zn(2phm-)Zu!^7 zJdG!|T^Z{fJjW{L2zFzpiyj3sOvhM3%paSo^NnF)1J2`72PxaGt#Io-pqx4p`o?GT(S&(D`I#kXLx!^b^J({3i zYbUkYq%BPqB%pr~?m=CpAa$P}A~!yS=jEdLX=VKVD4T&9LDT$vZG+YKtX*;r{~%QQ zwWOKO`*8vFjzN8|B^pb;;mAr7jBYY#w4w-YZIrL)Z->?RkOYo-#vQWlrgpSFD+_Ix zb(%Vc5ByjHbcuf)_Jj+!n?I*`Z}Hq6ot>4YVT>K^EVM?Dh5r6C85Hep8nqWzvSs&b zqsp_ojSWbw@E~5IrSN6`ypl!t7A2uRBf8$%iO^L`1zJ}pifQUEgz;Uzz0Z?XM67FM z%(l2>YceMuDGS@_Boy!Rok%x4ql_%RxpCEP$br&~s&_S-V$YX3sF=sYEL4&)gUDtC zJy6*-lytWkO3^mc)sewd-P^F&+Ya>|IPI&S{|nAIB%jpODOr2WN$uc!QwMOCqXRc> z)|e&<;tbE-y=(^=O^v(FU7Q&$b}F{Yva5(+Ckg9ZNa&b~-~KxhhTX=|bfWnX-5~(D z4p%gps1jgAo}z$wLiyW6t!RmJ;jRrw68<(K~wCY5z8RWHzfBT*2^Wy017S%NMNB z3;EU8ND8cs7M?AW&cDH|3^NR-s)}>6EADc!!vT!)oO63-5C08}3#-@>okuydX%amB z$W{b*-)@6bJOB0B!7arK`oq0JvBXl>-g_<#?0Rf zpZ(qTFtk2AN2ozt zb>N#VmvUV2WLYkDe+DliwkMlWMx;$z?G0kJSR}+1D~p=dY(!8k5iXij7QtACXFzB3 zZux_!50gtK-u=@F)`l*`)kJ{1C&TG`%O2T>R>wvSFv2Q9MujZ$3Fty-sDsM)CXwTK zrv{DzQAdfmkrq(VwnGt!OO6_kE};13jy<|=`T$@>fj?6h)vh2d4Fqji?OO8U{MX3d zY1{|5=et{i1*&XL)VY;T`Aa2sIHIep)6S9i;(LFk8;ZD!$2pu^zh4L``aF+Ac3PWe zDLoB6b3Q{B$>)50I7uCooLHoxWFW5%mST^zUTSGSVXe-qJA+0n=%CNc)TW~Yy`89L z1~oDPrE7-*k6xbgU^*F}YTX7g5jI7mw3MlD8N_>g8Ox4zGWHY0Q+-DbG-bP~o+v?I zGqzFEuCanZGL%;&tz+kF0t^QpypMwZ*E&B14j;LHY>XAcead}eKcIe;MJ9N`+^IMSesiS`#(D>Y)&A@wd z-q+APweki(R(7-wXkrtm!)-oJh3#>OObUd;7|W^eya^AidwFn@pzHP0R#eev$uo<@3@`95a68|cGn2mXbD}kOa-w6Vm0rh1f+$kZfd(cz??Z#O;3)U55^uJhAt6R5FaaIU zEp2-t$oHG^wrs3*?w(FFGIr17rrwK#=N7L0c7k}OK?<&RT$wRU~4LO;gh=M07O8Qq_}$*UVVOu9^L%UfN>DEJ$p>56{j+YMt>R zNtQd-Vw-#k(z5BuL>g(Ti}O`UGHTG| zw4F6f9)DGt_I2{)StXVxUThP76NaD_I;ocVF-{}#OK)Sk2LFNx2L^g{leeC8#vu1H z2EW3Cfm_PI>d@=ISIq>x5eb;Q-K4sJ;5y-Cm9CSuW)>?_4rxjw!{VXcMNXF%gpDT+ zt|bu&GRbi~i7^~*XZcUxTut+u#NDXEHVQdI^b6e7$cm#bzOqj99`aREP4>?bs5?f} z>Vu8J4_C$Z)6rQGs$U(Gnb`>-OLjH37UpA^(+FY7- zt^U8z^M{4=#mu7R*SKCUy$c$WQe< zm41xy9AL;#(m`~gb>d6!ENtS+3A zeOvwSH>u1wmrZgDBw94>_*{rR@PSQ`33psV!*{aIVC!Xyox3K3a2ibl* zKJ>>H^??ZA12N|a6~dZ7jOZpPz}HaPukKC^_BEy0q1^I-?pY2Zn#YVJhX58}g_dRb zoTFTaSN(nY$0t#l=S$jI4kt7-Z0vRGKD~q|oSX^hp!a&<>+ra;Hf2dZ!I~dB=vN~I zlYB919J}^O^5j-&Ja*!<`qr&`Zfpc?WTJ+w{UY^EsUNL2p$X(^bWNK?W8*(yA1qIu&SYvY~!fVIRgOcv7 zLh)|n+noHWj`0mvAL`m@W_btZt$t>r<0bi&Y9zw4>ribV`=x38JGUuUrJIBzh^d!{H#88Ql$9SefN zfi$H#eCxz~AY-q}kz>|ky#D??rP6UTTUh;Qdx44_F;;Lr#me0C$w=eO5b#MgQJ?3) z)#uAAeMT}xD3ddcc=0Ck_+||f%dj=J&X6md^4rJo(<_kVY7BNLz@783u99%*=U!=J z$^x7@FpZZTX4-V%(xjwg6KdFDL=S70qoeYuC@01aF(1PfIB9Mo(8YQ$KkU-v4Xe2C zuhK&)Y2oWqsa#_guyB@EIR86cHnO>W7zL9X5+I}DXg%QK6A^P1CP$d2)qro3HYk`sb$$nO-u3=h~>y_{a#P^@T#* zyFx)Od2iuwj_@~LeF`99L*>Yqo4E!N;*L=wHyt#N1Qi0BC6c_T*kdfju7?);fMLg^ zCo7LhM8ngs2Xc~#XyYoWqzuA?tE!yffrp|)Gu*ZD2Z;Jnd6oNEpd|ySF0{0AsK31( zg-e=%)K^m3Jr>6e+h`WT$kPNOL3b;WH{3JWb>a^d`!GoP%sD29MaQ@C#;zh0VuXb( z#oxmHbz-Xr*e=5}#SMVWwi}_mo|sT6P|Xgx{nEYd!&>{QKj(vh7@taj@T)?e#_V8? z?mWHYNjq(%P+RGd)!o16Fsh!sFV6we%l#g96PeHrWjBT5i}klb%cOI{vvav z+|N+MlOqj&maY#n$rPw6-3o)S&A5oqstyX{Fq(lkLJEfuew|JpJ2^=QRfW^9&cVAcJ3TB*wB)AV) zVdJX*#gEKbTjK@|Q2^FBs3PGoUNGOsk2KwQX_Wlm#hMnqTLv%deUET%2?snzTJ@3Z?7k6>j8|2@cPUzcW zAs=*Xx55xrIPt>7ea$*la*TP-KSnpklAwxXg+DvUyO+K|`JqaB=QXpmeb1`W;U-b< zK~Ms4$nVHB4hjdTiLVCqMzdt|4o9X$=X@#jcgvzd1J*6SxMj`^Q-0G`BmfejQ2f#{ z=Ir`noToSD7s56A$tr@gx__37V|Hj3fgj1jxq?6iM) zz6)PzQ3=sP~*y}k{I++-?QykoYF zLGj1tA|h;dFHShd*ad(ohcsq7$&(BU97ZcdHgzmT!1OM!d<^vBKl`JsZa-Sp{}a4; z9J{xQ^p&d?Y+=0c{dxPZ!<;sG0p7=>&-!;)GBH~1nu^+7;KzXwQ7lF_T4?e{&lxaP?>xvgu=KdD1hTS7CEcud?y36gR^dr& zY1@R4hdA56gv63yxcF_}TV<1(yPf;AmkxDv`_o5s5GYw?TOb$fd86#dYYKkYfap{B zC!{!=xOBd{<=2ZC;^Odid#i$R9=Pzlw+MZ14rl5_cs$y>bM*f zv;+h`0;V?D#t4?#CZd%7$hL$W$0EHx@qW5lPM z!V@oSs4FTV;A^j76-T|-OKW!e_m?RyZL+JGe7mwFNr-!x<5G4y(LS3FwNSy8%2&)n z<3d_v^Y{m@aUuH*V9ag2)^9M*#M3}Q(OF+nUM#DC#20Rs=mGt%f~m3==|)DA&=(7` zB7=Y-H`1+*J;Qd;i2sKn*H%Hz3Q)rj+naCvZ0Mn*ilFn(*-!zLUEC-7TKhsOlWxc?=*dzEqaw{Z+o_k8iE-#AcD-JUO9Z zUiiotaN`7z7Ux;)y1Z3B{NPQEbS?K&H!!z!esXQNTD-Q54{Op_FUX05Z7TZ=!sCK- ztW1Ucp9Vq~9~Lx_=M&H$kldX6X{{Kt8GWGfrC&kv&snah<+Otx|G^~aIwt39()_IPp$Mn zsFMG8V1C8nK326Uh+g)`Vx4e-eG;{Xwm9ZppR#iFWN0Iib z3^&<905ACupU|{L0qiFrU>ue{AFDIY^)n}1AR zSKf09#%p7iIO}Ze6{voN#X?1M=I5XhPwODa5KsboXxA^u{Vo{3H^BVl5oXA>@#a5< z^S5M-8c9Qr4S^h9N-9JP|CojHb@){LcmJ2yp)@5lKE}VI1QtXVbreDAvR=w0txVE< z>>V@-6ZWTU9qzdZ4{1g6z6;*=U~}$STl6It%#(jZl=i|9I0n777>_TBqS=(GA8#UG z0V*ZB*Km2%`)J(`d|{zac*1Qy>XEiU(!z_K8EPDG7pD}-SM@v3)<CiBXzAEo`8}AUjHxZ-U6zQEn6GixWmTXH9&B8cL?roL4pN`;Ox&x2uUK)miBUS3{4X+n36 z`4-(%9fFYFZf7S5c*uMR76{b7l7tWuoRqiQwEeb<@vrD`-zQmUx=%|p#~)EFKY2N} z7hzkzs*&t>?Wy44aL|6$`CQJnbN`FM_!mk|7W8REb5UN|98z3{d0OEY zR`pNqXYvB^vTZOjZPkDZ>11z#%1YP}c>nSjmFAG67hfE?+=~&Ifq;KwKrR`6;$Y0~ zt3hC~r^noyZLUQJ%%j&59^E4Y1!gUP`m?PapjA$LgAlYPR!P>8Z%YbBN~ z`!RDgdd?i1nx4UN@ny3d0F6V%M=h=RJy;Jjp3o$o;rtJd!GAK&bugN%YC{@ z{;8~JCS75Ym+p3QqS5`>O7eLE!*&c7-Zk+f1TwrnaN)Q$#*QtJ`Z|cKQ)j)zi{bJu z6Moh_?;`#e@#?AX_`XSIxK3smYT$u!AV+tgESECg%@w4kuQ-)js{$lpu(TEC`~K41%c8gCHuJAc#se2%=&%ixA&a z{2#FzNvN`Gi+B?g4?yg>(TCrr-9k@%74)Cf0zyy=a%K_SKp;PRjh0rmuLI?vDh6-- z3__oh#^UtQ>&tSUWe%4!x^X4p8JH$(KS3pVCw=jZv0SR}5h-u;_yHhpANs|F3@Q)7 z+wKr)iPjK*OqzA`N^!KN4^L>@c@U7cNOYt1ARekwVYMzm+9u>MbP5YCE8uI-nevn@6WRkdt~J z%2Tzd4>kIq;qN}RO!=R`Z)$4Z5j>?!IThD?HCdT{Q#cEY18e5O6p z$?&}ONaUEGZuca;w$qv#S4)ORxs$nzf`3RYt(BXs6?PDy^nOhjMU~_fts(!Y zve5`(s)5#Ws<~8PjHl$7YJak{zPg_D8T-nyNo%; zdrZCyYjdpW`2g)^n!6`2U9##7A#<96&f8_*iI*tpGn)@DVN7rXU3m{=!?a8(ucV@*cI1OXZV=a$4)cGm6i}Sstuh^Q|uM} zw(`qo0_`&LwVb>)4Y}^t@8GZ0Z!7uz3~I>tEM8;77w`FHbCxJ&C(M>FakSEaL|jhP zkSLs{?6lv25J2KG8{oVuCIk;t)({!%X(0BhJ)w~z71pw6G!wYX5`LyDtF_oYdqF^U z#fkJO{H@}&q&%kf0_rRmU~0Su++#+6>{Fc}_TIODzFZOkXlM;F~8_|d zP={2*zrqtAEwKUyM_|Im1+=>`-_OekA`4XeVF$F4IFPSLuY03b;w)SFQh(rX>2A^gY=B$`67zpcQ+elf5=0#g3lJgxh^F^a zH2Cf+2f4ec#{Yg@X62Pvx@d`%VB29JVt8bODJFUv=R|){^6d86tFyS2mc(XusNQtx zDQNIlQG(WigKtF0+~_$%8=^2MkcSJg>hW$$)@|$2QykQv;c5nOI5l!?y4sFa-Cu$A z>V5zQ#Z4vE!pgaQ7kOSV5NR;qY+As;8`WXA%nRDQp^{8VNH#8SB{${}xBrS8R#m-QIq%LUZA6-3~4 zVdi>uBNX!ckc}E)&m>ufQ?-Lboe}>HeHTWn^>ACtz?at# zkI!7VKkegR+26-n{K@gYTkB`x*O~p_I_WrbNaRUjcp^DmT%0*DOjA9XY^@@)4y28It(mkVyAF0DygDP+lLd}Z<|n$It=8|~ zy>Tv)7reed;9Pkd@}h8z^PFcb`QoON1X-!(83CdO+P>zO3D%%fY7N5vJU5W36}Jnj zNst_5Q%Sl;sw@ph2x}AFASY|>IP_M8SEYOon{Td93#~P*pEFi33Y%*-Nn-ezw!Lql zLOW_6#4xPDAn1{VBr@d)CaaJ-5w~#}3s(-0dS|I|DL^@WFBoeQC|19U_Os!?{@Z9~zHM+-gs?O}Qvj)a8Bn%H+ zfbr9EKzff_VMvLJt#^{LmG^VN-OQ$|oSm5Sw2z&hP27$uQb&z;(x9^=L29NFqM--xyQ=^}%8pSBOCF%tYajgaFz@uW2KtD{v;4{*flVn2{V`IRDEdHz!MA&n@RjX91E&q7zW@ zRR4N&l|AcbeEG2rgaauuN2#hhRKGf|WakB))NlO*f#=}2-~MGchuTV(s(fmW^_9F% zzm|%Aop-|PRrctzrUj5 z$m9jKp|ha}hc^-g3CT7$!HBQJke2wmP`#;*{fBz)@n_Fn`kmtYtu23Xvr(wJH zclP?z9d73B>R)16Q9iGQoRJTlYVrG}$hdI`Wsx_KI9(FacI|U{(@hb(e)vCW)Vxey zZh(G-H++LUK2k3+-Tc-Tb`*E%6U*f?H14g)NB@||?l4N-(ECdVbz&V@tAf8{Alqgj zTW>l?BMSEF`#NmVAD+~opyrT2r)$ZsfhVu_>w|nSHP1?oT3afN=&Atk??by9RbV=> zfjJGjjo-GI+}w~8WEfqY{ArH$Ues0{-ce0ahX0Gem8nANBqLnLWKf)oa-jQ zfK-;~;i~%wSB&c|iW*w}$dLY=fpsx_JYVCj)4AO=W@;a${N5)SXvDz4cF@10dbFE# z=1WAwNmLOU$><2@!>ss4Lv8kR)*PE{_25dMgUe6MjFh?_9YpKvcff&$UXvVE(@2Ly zD5bB=Mdf92NS@L-oxErxP0bR^8AUAa0kGcpg!*Z#ujUI5*h_28%qk;vpnH~&e{Tk3 zJibC$86l(2p@b?JnFgu-ZH43MGR-%?gnEh?_!IJ@A9dzxv>0=$j0M)eq2!gcD3)#` z>h?^-i;&n#Yuu!!(NaNZrWS1-qNGOUrxedTP%^v*0jiK76E?qt#;4xt3engUMI0jV zr-{ZEXg2I&2PVeH61wNemNF1p=?*D8SktwBB>W~Tnb`V@9foa|aQ3UG!i(EKk752d z#2sB~Z3E7)udOyL-hncdj;t_<%+=GEx#`QG`2a2jt7u2wG(DY?`4-t*jaS~KdyI3s z1&?Kk9cqSg?0dIZrINP$f18NtKUxYnX{D4Tu6@-B5-(9?zFjtHR@u&d;*d$78;16; zcCDVPbQZn`d0-h(bnRzvOba%l7d$|OURN&d_2q-y@&~PV2b%?5t2XSpGaiX4yhNHy za7_w9sKJs3ZhTvUh-q(eM>wX7A~R25$=5Sk3y?9g3;WSi)X|sG$s?DXiink=b?KX6 z$=ZcwA;v@*?UcQdmk@AX_<1lGf+g*0h#bTbBP(Bqym*1{QpDnn2%U02kSy2Y`zSoT z;+@(2$?}IC2$XFAQEIb>Y4%>y1Dl)i9}|@`>@0YZE=(TDc0%B|LuE zm-;p^CAOepS?6kH)Ky*bIzV|~M`;|PLfRRbR6M}QXxb*RK_=Xuvq z-QtMmBffH5?;aAtp%R3Z>z41d#wY$uJ8MY-&xlwoFD2=*TdFB;2w#O<=EtTt$ zl2y+1Uk2M6j6hSC+an)7BsO#R&sfkjwh$~c;X%wRb?K{r3CKrtBT$z=OxOjC%rP&S&Ddx8{ z1q*P-m`n8hx*C?6vl4#*zG`wBDS%{i?0x|DjC^)(6+*0PoP{fY-9wD`5_ApdiahX! z-S$2*P;iCpbeCK%t-5byyseYrK+4>Ht>)Owr`EP(>eU=A@e7zVs4LbwCHfIEAbvd* zwbKU?hHm2(632)(VKrMMiyJLDL@XWI;2zJ#m#{Iu?J!WTMah4~!T6zgCkCqipqUfx zi31naQL4gHb>f-zT}OIViQ$D{;M53o*Sijzg6K+k@2O2r>Ow?#J zvpK~_&)>CX!r1S(p|)uH;ISH>Q}U}SA(#GbjP<0fXcZES_43LTq9WuHO>)FKO1oA$tvw|dRj|Kndc;GGVTA@!QTxHgU? z>=V*fkygk@MdrQXUM#Tb$fi{v;Q{~1W}DkJu)TpXLj;^>3twv7Oj!!O)0Grfx)<#& zp&vw8U)DU(lpUwmVqP?hf*Ya>e{d==Y$Nl09#-A0T@P%fe5O=0TuZfULkzug=R5n5Z-U0 zH<<7Me+LcYH}i%Kmk0(yn;BHiCCSR*XpuE@TIgE1ymJdAkyDfg!toz9zvAbROsgY; zf`AbfB35gZd=`YUeiY}MT&YGRyy>&bvbKIA;CN-8sjakZk4Ts_V4h_;5d-L2J}@Cc zVIMMn@tm^0l#*3o{!Vkog@{S;gKz_#p4a_TS7p3wR>72;C@j9!f)8Eh>sq4 z3B$8d*v;kOVVZJg1=Z9+Q1dR~F(T{uzVG*yve?(_jSD_+x<*sZ6gB$ZYj$MaN>>vQ ze(Q8E?E_ttpU1SGF~niL_Pth%c=Sf>nFd^oS9NZ^=IhV0tR$YPHaF-8=v{3V-n-cn z{=?4*(%+n#*T@UHi0f^h-ij%(+kGd0Xm%iD(nCm>wy9?^3o1p(RNLda8?N5m#QFo^ zsC`km&DGBd67EL&?L0iZHGF`m>OGR2nJ{I(O@KbIlQe!;-OL~T8MS)x6xWQb(OL#- z5TtM7WA^KRW1o^2EZ>0NAF^e*J8!LRR?qptG$R$gPP`oZsS%C1V=x>Y&b5~a@YX%b zB=*_p6#7Pa&`rteRV$L6kU&$YC$?#8mEmjE6v=C)DjuqON+@U*i;h0^_(`G5c%3W0 zDw4c59DUw&?@VN~n6U|TBLCL=dd!v^ms`i^#O%*w*u-sV6Ge)O3`Zu)4 zOdZUEGm6^=vEJDEwDBMU(kEf0C^CG&B_mMq;q9;)Gue=E zo5~P}%XZdwZy8Wh7{%w3@R|AB#WCO5j$IgW>HosXGg&cW>s~v;5v61bk`Og<;6)AT zKUg`G^Qoteo*_4obK>Juj`rGmv<2&d8fV8W*AP zx|vFt2wYZY2L0m?K+F%o@%tYDkmY+;5NU>5n`uNQwBj$zZMw2-{fh&YbAi8e($c<; z{|~R9i^p)P)wagbscbKr^x403@`G|mp~TC{jIh#~xKEdMR9BC&TMYLK{>5T6O3#4L zq-}o6i*+e4cN2~T>Zp9&_o zkx5gQZZ)R1+!9wb5ev7e~`XZTsYPc38X78e26QOV?j`7LJ(9zl> zvZb-eZM&*o0Q!vU>J?MyeU-Jmq_70nHrf{Cny@`=9dZN~6BMk-TOnMyKJ_9a=DKO< z%nR+0b{2UKTu6%(1SBw55ujpaU%1bIqIT*XnwLRfieEm=PXkrC|Cf=p`7=x3^jZgI zD`xqG2F!ldraB$n>6&O(G$<9{F?}+fLQC_4(2LY2`@;P(q0kji#d>Y5<%#XXH__8T zKre?W-%YmCP3G0=?+F5bR)u4>4<5-+?g(z>TTGEZ07c+Gg{MJER^vY@la*KOac%2X~)q-iO(D{)nh#cuC*wvJ!NVL#rWOUn*ZOL>{2xx zeSe!})P^I=HMMhM1=Spm6c9!Em8A)Y8F09JM;K#XMS>h(;vaRvSBdS}s+N2g?~vJx z!_?RB^0&DPA1iWYSImdRyUkmvb#>%JBPgut zHSmDaZZ(F7l2+Zt>m#%F!}6Lm3qj_5Y{8y0mqwJio2~;DG6v)ZwkfgQ_~b$uEj(Dw zY-#$c&2+mv3m_(#*QfU z3rUS^XDR${t2<53R4B~VrtH?y4C9c$H3Tz$ifC*Ij=qDDqp!=$C{1IJ5+A^3x|IOs zAV3KNy;=U94_>aEZ9Vq?U|EB47+osvqaZ`T`Ze!4O16$2vNwUbf-ht4qI~Z*L98!IrK6rbQd`rNOI}rh#Wa-&FPXfmIdHH-ch(J8@W@Y zUu@zdO8DwuS4;O+EM-@$#CFOjb6lw&v;o!k`K&t+H>v(=_H-w@IKH)ie#&f^;XO-T zSXj!%s-%tEGibMZXVW{S0M*Q=>&}O6VoTcg%h(g0Zs9=C&h$q*y3muub$3OwaB`O) za5|m`LviO+w})`~ zvoUlecwkG{N5>*_z`%c&g<+0)iu_F7QkcC&E@M&@DNEOD zVBQ3#NP3&;j?C{}Mw)yYqQ+}kk@{A4&%!U^gs1Ov3fyP8BySe9k}5aYD@cIV(A7&0 z@xC$dYw9D*}o6%RTObqPL{_E#gGqbQCw^zO6>%vi{Y+x3WZh+PR0~tL0PUBxNHQ zY|P?C-)2uWNm$38PvHTVMMM_MnK%?8qfQ6HHR$HDp~CIpVKse;v6EyryzlNxE53Z4 zbs0bzN=%{87wNRu267W#^EfVHo{=E5=mpaU(hbbD3pRebVAkChCo z5n~ ztLCas^@{f#qxz?Ff<2q-s28fHMFNDZ@4|xcA0uBF!G2dZfIv7m*lMe5gwy8EZ&zwl zeOpj*E&jEA!o6R5%?S4gK;h5!FZjSJO%|oV4qnRGWsSl=%39>zdxtc9y;uJ3kf0mq zs9V^hjXaHS6nI@uVI&eeZc}s9!WEwlfNncj~ zDG+J+{7Eg)RS?7@b_{HqG{WC}G=7yWjUN#O0P~T%_>yM=cd>S}`U8-%RXnNdfk#`* za*P@GQO(qBP7BvrT|!UofBr$Ehau;uPLk7U*DXlvhjYL$)GH6xWrLj_`$DqC(B7*I z-YZwvMp}pM0A937o>8#CS*_9)FK-H8sov78iDrN`Gm5W1>QD?DV68LPxVwFzbHEmz zwymP=Y;4prG#$_mH#0Pl(8U4PYj&ZGaz=~g4I7lVTpipLMVTDf-|Wk*rp;)>tNIm8 zf_vfB-d#o^3W>nntzyGxiH+qzwT*JZi)~|tJU@qKKq%i^nw8m<*ER|kJBgG|PmaM1 z`MBSdOwL4Re5+*8I!{;@(A{TWJI`l&Br?sPn=bGqw;b=0_$TIIlN?Mh@XL7S)|us{ z+JxY?YkMo*XZ=&>fgklR^ORm%aEx0aOHDj7UWX%2>u$&3)x0bCs$&|>B6~($1W9Ht z!n^9B2F(Ee8#@7wz1q-$tEEe>=}upXTFw`{Y@dps@!Fm2 z5zd(4bpPJg{$E@($0HMy>mW7ba1P=#8;s1nJ~6X!&U6rnx|B_kj?u#YkW`(<#OP(- zw@N)7Qh1HjkNw<4b|tJ=A6<8!t))2(C(hcJWK~$W{HMk%>>$os^{Sri9MR;~q?$=E z2T^i8|SugK^q;O#n@urM zr22AeE(eT@$s~S!&ojjkV9R&TnSDr_gR?v<=0=KnrkVgjEmOY=xv*Y79Jgs| zsm29Y)vjd@D|GtDI~yMp4jx81%g!;}Q2iFV1ZfNk<9~WwJ#}_%e$lvbBl_7;?V%u>xe7>B(>z{%Mfq#g6yxMphD=;qD%>5|>-&67L z=`+RqsH-R6MmpJ-GZo@KWe&f4>A!?n0vEb=e2S4X2COdxzst`&ATqZ21aD(0N0ZI8 zDV&F(Wg(llo{!sPkP9CDs<;7?pRU!uKHQj?^uE*m36D>>+_pGgo!cva$VKL#4arIW zOwClzpm7t99G@9%0CQP<&w4UdUr-9VI2^LhqbAXyP<`fRp5l zKymkm#~g*6!I|^6w|G#0|7MP??#zmH5Ky*A9`Eb=iZS7BFn7_Gd=QdIn zn2-TM<Eq;3VO*UJn=O#bY%y0r%;Jl;0sIh1Zcvb5|q-|q5I$cu1aEvz>}u^HH4R6gj3Hr15MlIm*?4*JlDW| zOx9dZNw2J=JB_O)PmgY95XmdDkeAY^vYTvq6=xIb6^PC9BB;tcUry3Q=%4Qb7XT;S z+?FFA*apRHw_-I;G4<@n#~g)4ztmPCOKQQ`Wd71;_7RNyOG)hzx*oX$5%^zo@1VFf z`T!;(f3_$c3F82aMRMK3H=J59KT;SPU_knv6m~Cas(HJ#{`x)4>9iK#q53_6Jgg*z zZDfX;jt;2ddZr#QKD#wlNt_CM9!hh7MSs z!$pFDj2ceb@>2gU$X|K8b1I~1)BQ1sKH5Zzp>;1jsTYsAuu(IUuw1OTi@+pD8~C~J z>~;N>0;NiT^xFai`38&WP``=yHf;eS$WDx?pL&CYT*Xz}ri}a(a~bordHm~*ycf=> zTCc5v1V=d!e4tpWP>h_dvX=5{@wjsPFU|)_>#|R?Q&qi@W(NY7^zSMFI0m3>2~r=< zJ)1LTa)M@geOjMc{yg1lsv0dz_V5wua<#8R70unOFBx#Zr1pvOnKRy8No2UJb1Lsg zt)3c|Wpo2n&aRTR$wG}ljxbz>~nzmGXQK}KB6jCO>FVSA0S1!wlI!G? z!H$`*>>bs#$)zi=Wox2ykMF-Eb6R*+e3_eY4Q^Sr?P@WAO;_B|O+e-GQ(I>&WEi}; zp>jTT_)}SOW0n~Yo&>WD=-_P&g)CB=${i^6ZB9&M_2u9uE;60aDakjc)VHf?>#kHy zGFd7Q83lIw z@ea}7h8D##-fo{N6BQ8a9me4%ST{os$W8WhT+R`IYHTTI@Yu>l>h4cgl)5Y?^chb5 z0K|7~pOh#`;ORp@4hmNqh6}7LC@K&8;3p~te?(@F8!yM{Rl1%NGujak#n8N4Omrj% z=_Id>u3sSa{9PD`*l9!m+`OU#abR>j8teDNAF@V0-9*-UITK^7_HKuOv(V<_;EC!X z!QXFYalhYp(MYV-VZf;o6Bjt+z+xF#tp_rtG@e9nOWsA@PxSftpWU*K6O)(n6mRF3SP@#CC;+BTCKCDS~6JVm#*68=w#10QL& zA*Oa5RN+UB^AqtWX)@W68VRGyrX_I3S7yKRDK|kO2(4?EC4Xfm=e3hdR)<|46)BnK z!Y8e_)G@;EZ`@Ah!(g!KCDjzgAD4bpO?ynmzCh$mFu8trP4x)uzm8YRCq3QIk#P65 znMG1=kF)W6wtDV;xd8i9D&*W#6?}Ca!M0nzCF(hY?RTmawW*IFvCpz0cQ@7wYKQ~` zbQUhgrU+1EkcQ6IjYkq{p}_NdFU1c2Ppe5DE-kdTLW7ZEe?{>z$}4X_JC!LkJ0b@W zck~!X1EL!=krE~yUg?nJR0}+Df+V0)PC->2{_vPpO|SzAR>d^TKodd{(YFI0JM9kZ z%@?|^mAngtC;x>nZV=hs4iG%i)8`N9UK_dItM9(%pF94qZx1>oqGd6D*r)01y+2vihmx2QSc6ZI8(`&r7tkR6D*mt+B%reu!84>5~R~ zxs`?1owb8Q!plm7#^vH#*PRZBjfcz)J-U@w`unLh&(}{_3)Wf7{+!QQv;9%IcFO`2 z4US6#EfFwZR(-R^{+1X&j|R=8-{tkk^rSB^kVm^#oT-Ac)RmPL z8aJB@K*XlWozL_zU*Nemv(w&cS@Nw}d=WfGCm=EKJQvsw*F_6Auu%AzEdGb>v>W)-K**8S99ivoRPiXPv{=~mxYID=iBVxU1HFM&TAu~DDxbJ>0g)=ss@kcF!t>Ou( zm*6S|m*V!=c6_^|gQvtlgO8l{dC`O5^P0PNR*p*?B@P}xS|~4m4(FC6H?9zWx{l&K zJ+0k|`5@ENwq0%e+@WS{4%3vKBLKr1@LI8ZM^WQu{J|~A_rA;U{V<$gRo)ZO(Wsbj z>tIZ?D+pJ6r}U?YLOPBZj;?bz&WkJRdy^GRA|#y-t~qK$XYOy^U0bH-$#7Bh0$=9> zG_q*j&dotjF2YpPJMp-S7o=GqG(zIkCiryAuH0-Aff%$3%-fGC-!28d2=lL)Washi9A7O$T|N%`=Ei{_8tC)S*7AdX10=10KbKjD}^3; zWAEP~2v37!q}HyzRJW!lzO=8;M10`|5TtXCKO%$8ZcBf$TX=@CFX;t0{qO@2dXx2U zN)2`5E+x2cI1{VZDm^GLBog$fXGmVSXnT>L+ok1Ns|!g;oxTWNT~ z(twMIy&*)4cxVUA7MCz9u`(Qv1sO6q%2$8U7dBX1*h+xHz#uiRQbHOT_BLy9lx<_mZtRZR)w-rXm! znPP&Z?V6#IOGPI|BoNucSHdM>5;Xk*h(ja3s;nI(!`laEz}761$jcpd(uxj^O_2+R zsuJ9dghP2~w3=K(H0t!$VVUR&RX`~qsJX;Nib^Y=dwWyTt`Z2ml=H{HvKRbD4lepa zD}X5X2)DTBFAoJyT>{=Tq_bObt^SSf!`zG24*+{NA>yD@_LQ$5r0|`Avk9L$z8+_(n~IFyj<_Xs=Vg5UDERTDMP2SW4+TAG$26paT)Q!+Y-TS^Qa)LYQ@A1qWQG8;QAT^= zO4yp|&cp1Wx|1w%F?5LmR|r3r83xbvD>sm|L;4GCKAeS25zK_|fmCYacCJ(?i=EElptqxVT{E+hA+Fx;a>( z3p=&_sE`lmVnxMLh6#xcDZt6+ea+ZZH=hx*-^2C$VL>MALBF0H6oLR3UN9F5=%$Hl zs0v$4H;e|zKoY17j0OsCDs9m_+I2gA*7Q*=_H2yA%JmoXanG)Fgvj2jS6@mRE;3Yx z?9CSmGn`lQ#)ciJ99Je@w-$e-o0IOi9TJ;NoJv7{mC7A*6uV)R2cFIS1op<_DKpV< z5j#O|Azu$WQXkd4m3~3&DAfVHfDN2=+roIyOpLT12H!87w5XS!!lxe&<$8XZPLEfV z0Xb68RD=jp->iC;qS3b`GnOR{7hD0ESBlKAbw(?mj>$Fxa`GXp6-kxc z|GYg@t+5RU1&8%{z-g4*@qxyH-0v-ikhnBv*NsFsq`R+o@i{aSY%jff3i3Sy$Cn+O z#?eYOUUBfk)mgERb0A63!$>SSNL$kn$;m7t08yvZ8&Aw0BTYzsZX9@9JZ=))O?$& z_m&584rij+hnvpErAb7WyAG%P(ET|x6RS~hRr916~d(1sa;`dQ9Wx>BxURMz|4I{1?P@k4mJXQ87lV)@$dK8weK6dADG&H zKLpmCZqO2|^ueB-q{R?4=Un*7R}{a{&-m5bw7M~^{X7GuDIkC+6cPvyfMQ#Lg;B!G zxl=;n+(O>i1{t0c8_degD&}Oo&o@r$i#vG&le3Upo#Yc75DE2fD|$i03gA#cfSB+iU=s*wFd7TDRb3)RGAzY?(IkvK ziYZ{*1cQ`fNCyK??oYyQB#pj}O^qgZ{72iEqxXfoCXtJClt}D>Pf!*eVowV8h?=}K z&$ELUgvGCx=GBqlOAE{u2B}Q}66CO$VLrT-bQrsm3b$W$P2)46k1bM5Bxia!b5r{fxc%q+UwW^?)irG)&=VTAq|#t1dI7 zg+PgeO=~hyPp{xaDwLH{%UgN&ioJ- z2Y#4c2rUQdun<>*sVbKu+uQ)77d9!M{wfx_K8_*y39UmbQd)U}z^PoU%saK*PrHyI z->uwm;x%1FImz>Z+|h?2d`309%py!U;vXg(W(5a9hnwm>_L6Ta-E(3WLj|J{VQVk*`51%yJ4)Oi&$C9yv`{^s0=nIDl+m1KYpasgY>Em=haJNq=E#~XMF_mc8 z%&=DXCz6z~xQHuOeS>ePAh(ZzlUWWTD7=trY*=a`gRjiuixCkN`gTZ^jZzR+E5_g&amy3yj9{0Ph8 zw!Gi_ue>Dd`k^^|x~Up9Mc~{8+*9~T7{X+WYr&3^IFX)v^ZpliEfe;Y(XKFEeW#}@>YABccjH1@11C;)u#vPH%0(3U!$+e&Gi{% zCXw3Ex~XiV)&#Cj&MA%5x93T(VY+l_DrYy)@LNFzv#{npj73M1L~~5K0OQ8esgy(8 z-X3OTdocvAsKW9~xFPuU&%T=w=r^=^$X#zYeZiK^sRdKUksS{7EjB5I6qb7Yp?YG} zkCZ1rV0MR}$zPGS6<%D)n>S+gxE0?u?fUq1#)8wFM8KVXd~2ZTnu@(?o`WhiMa(4( zDxJV|=$FC?Q{q%&AppN6?o#*=yEfx`BP|Sk4?U^W1>|l&QKl&eW7U>$Q&jW`bZ||1 zIIgq*SU6jtpMK5$b{Xtr1B0*;*I(L;Q$?^r)Z|SV5@-v+F6@YDBY{l3q~9{&J-|Gy&z@IUlR58^ z6P~Vtvp4uWy(7;uW#!$-Iy;s_hKMpl|Bl`i)FvOgS*p1^{VqxGjM0lQvT&A^{{TRU zUauSVy>%LT^eRnc48C|+KDck+1ce;ExuH%jxDXgcCm^-(nuI7cWor~sqO*S^od}o2 zI7*7cX_<5$WmPEUH4bVBk%?iXkdJYTTtqyXxn^$~_na9(7@w_h)H@l92Gw@AnqW5@ z4To@oa>U%&<6V>yJ!SLOO+FqNJYqKlo4@D1?;g*gt_a&FLs4KxByNp&=yQ?;4=G8! z6ePw#D#RK7Bv5$OtzWcsTrZ8&F!`NAXs|}FpcpcvI2x+_MI~6w^q#L6a;VpwIv!a9 z7?UWlZnhSt6wzLKSLq#(Dg6tcpo{y*HV-Lkl$Ev0j)lLY$V#10w2)nSaUMR>cB+)dZ zML(LAo{GmqwQSX2wO}KTfKc?gYQ_e^U6>kA|?b;?37V)zHk5mF`F+lMKKn!Qp0-%VE z=gMom1K&SwwOhuUI<8_kMGIk@N)T&3RLrTT(gdNDA&kjPABni8eJVFvW7-r(D}c&1 zCXvJbF8&C}*NclJVWO9?mlJv`$mILJ+rrSB2*%Lg-4|{HS?bkLET9fOn!rW6+1P@6 z#ssU)hzZty(=a7F8{LVT_Z0U`D}e4D99X=;>I;BN{m4Nu1HX<_q;NlTKVND;445l*b*@ToU3TwDVBpr(8X&;_X>Pbq?_&Th*o4ryYT zu@U14r-nX@6e3Yb!$)Qq3T2yi0wR%Pm?2nu?_kZhIDAQpWz8IbiVz|!aQ=pfp}90y z+b}J;;WwQn9;2s_+_p6)#i1f&;V4%jdFZ+|btp}@Wbv9*NFo4-a2yBND8FFknEF{M zNmPDxi`+NlDMV7_&^{2|koPITlyTViIjt&|QdCqlRv~;i_wrb5atN5a`-)u9nZ5lq zhP_}DCQuuR$TE-*YIu-?sXY#4{=m;dP9|;?hUd{?eqw0valDENLIK>E^PWt~&rxyq zDw_qB+0U_i zw+swtMURufCG+AU10oCO2*EX2wPDoaWcbnxDU*oEf}#0FZGDNKVJZy6mdZgSLFew7 zZdTGO`9i_zwDycI*S}&eWx5MVZ=c849r#uCl)KZvNgR69UpSmuL>b#kP(AfVQHd?# z_EbAC)Slhgmr&JMPIs=FP0cc%=A9%IMEOBshwQfp zL%$^e1_AJ6YB(MQq)J}&7|e0r5l+|R%gyq4Hn{SlVm7niH9W0{s|fS9zlq7Hs4&9J1jSlLi(*O(NU z%RN>!S}cJGL#(UrJB6pN5tWDt>iu;H+Hx)-lU#h9tX$%pwz)V2Hj*mlb?6N#4itb$ zw8g5VboNVly=lmu1=*IrNzY)NokTfmtZZ=z>7l4e&i=;>3PiZ8TcSs_!#X9-KwK>D zJ{IOEL~)NiTSNwmIzPoT&r$r+PJbqRNN%&r#4@>1ojf8gkTM3Meo9{M4iIjw+X`mt z&60)G7J7MLe-lb9!bQcr)siZ1Cxy@^9pKz8j73B1)Jl3J3RdkoN3$y8i0Y2m;w{A6+HDocQW3EaT z+)al|j)k67R*VAD6b`*b38ly6C6e-m14j(Ku?0C9Ik{eqE9Uj43m~~dwo0!-CFt&t zK;Kwu$k!UiMz_U~H)L-*RXTg_6@UYG>Zws76wLCIhl)xqQn!-v=$U`Aj$wnQA3D$I zwZ7CnzZ>C)$OXsC-Hk>~V~Swm>Z^~rMR*-9zK!a>$m-`mBM?6GsqR#}6_)QBEbn#$ zdj>u=oXQ?lU>Aqf%RVm_jy7}SN>b0)n8>WwBM=ajzpn#RJsLfY9tWKR%m`zri>F!y zx2Cxis&Qar4>os!_AT}bGy=qW1y*!2txy-3&Ho?f-UF(st!o$F=>bAWp#%t! zgaDz4p?3)on$kf z9qHYX^#YlhwFh4OxCn-&*kuHA0840;w&NzGiU|3cp&>dZ#2e~suv&hFt_q=o2P-2~ ze^jpc3Y|xbnX#y}^=ivwdU5rTN$JWLMZIG|jEqpy%*}P$>)cwCm&vxk4GQUli@j)8 zPvl*RE07bt>S}xai(okWFr3HQ5n%kc^Bfm z+R_=0n5wCoTgMUwk9PlP?bsZ=#8uF)qcQ1f=*f&waA-<^q^9x(V0i9zZr#sHS$X=~ zvci(0THyXPYsKi){6`a44&+S|`CcK@sjMqFh7u53I7&I$sj3`d(=q1Qr1o ziHFYm+LdauSz#PO~oZTp}I_rjAH&-u~xYL{3I|DX8D4pmcnCrJ(eR4lZt(}#@gY-J~m3w12 z8-V^e=DM+NS8q3>1&_27v-~^uz0qte-mEQp!yVacO#h11AEX0`;#-@I5Osl}<=`ps zK`P`-VoI-AZ`yD7CeP+I<*~mo=-%<`HizmqG2e&`{VCXSf@3xkuNT(^UG$-4in)~^ zb5X3hBt_#2Y6;~S0+~?b&Nj#)?N@;Smm|b--9IzAdLd;c^@*SvbotnyD1NDr)w|nU zVL7N*sNhTA-C+5Ur(ac-E*3CdlJAKK`eAANPn6uEr>|wb_U5}rZqAm7v2b|Bx=Zlv zj$uc$e|heUxYPh=1fsw!lW|j!Ne(1zE^UkmV)0O|`LsE!K zsuWT0uQ65c8TWjBkM{=<`vDeOQT)$F#d`Z?qH`tg!bSFX%jlVcxDRmN1Q(ipV03Kk zzxobZyy1d;?CAJ~b&EJ%Ia?uVw%$yAXtP?zE)*MRQR#{hoAQ@|Z#?$VSK_Hqi_7to zvU106D7XhJZ)5F<@`vS0{?A<8FNN%NcG<3WLQ1fhCa#HZ5B%*7ha2es++0eV~ z+QSRZ-gMEj9rI#vo0k=?zWy;2tR!j5@$hJKwLJ{Tsnb(s?U&JrwC%_C9Xq=Hn4ck& zy2UO8AstG(TYTK|ee6q-ZfpvXC{d|??qAnUyWXDL3#W5;W!Z-{Wro~`B`>R1^*qb1 z(0|DBKx^zZ+Lxp;8dAfXfn!Gci^z7!0W>!5DFn&h2ag zg*311mib@xxhsl!SdkVQ;|?l}oxOpsP=rZB-Zqfq4-wS+0{BptB%+U9VzZ$;A;w=w zaT&?mONYiwW@znq5K6I8BfLvBE$K}lz>*Ly(r}N4XmD}bsQ3`rh*0kFlwdgA88(KY zcND1%=;Dy{n6XgDHGRR67u82u%7)b(Wg@=SFrhN2@W6R(b5=I{2q*-0!d`>;|y*x*HU@ zG1hZpGg>9++?sx#gj#^j!>$=pV$+RyNh>LGqwlq~=zEQxMsX@bdk$JP)G}rxF9tht z^Nv!M8HQ2vN_gX=>=X7DqI{}fX{U-W1su}Fuus8HT@nL?5c4*K*?1Ws^k>ZF3 zOUoohEjB${O}{A@W0awvPM>P7M#cG`K3qG*IwD?7KkbSarD4?kDRFk~-cabqpf1*ZY|MUEH8hSx4BHxPL zbg@8(Z4oI|is?&K>m5f%q$BeWW!tjnYem!-h1B#K3Rnv}h$xBlV$dbCvMYt>2|0>- zA|BT?J2u5g+Rd1DRZ3|?pvCys3sBJnJlCV5_I116R>0YwTx30kT*Y{87qiaG7m1lD zZv+e9QQwKzc3Ye(t*S@|5s=rm>gwJ5!DpPO zwA-*01Kl0HGbmK}m^a^UoykBeWEg{ECU#2;(Vh3}XrHJqR5vOE+hCH^m7HE^jnOiQV*UqLPPn&B9FwHRDu5upk(a$53k=7(=tfMe&|AJV+9f!;4g$|V zwmWz6hFe^_DxFea431&@1P72@`*dUoa@jJl$Pyxr2LjF~ih~cfQj+nl6m0#{S=T$h z@e}OC-$3+voENmqVIv+v?Rldngxj8&9ld(>`+-}O%2@WRHc;{*(>4%+dhP|+Iz5ZR zN>o+t?NmH?S2y{QN}`6vh}z-<%$6?+#f39rJ2?&Rg94bP01GZ@bU$SmG~f8uckAoO zio<+H4gvV;y!#D>D zw-C?sHX&o}^NbW0N#%evx1kSfL+K&b+P|83Jd5U4KCO-eSZBu+05-+}G+AY>lbt;H zMcN_>c4f)Z3uA%NQh5%C!nG+SMm}Z*nRS^`#hUy#XTy0-ylLT!oZ` zjJrcQMSW~7XGqBU$U3|AhG(YsNX8}<$`e5zxYk~zdr%kNH+ap#iK-*dD`Pgvl96tO za>mM|(Fu`Gf_w=ECm^aFd3C?HVW?bU_8Ers)~$L}T8!^Er+b7!&N^D?376tTGKrPU z)_G8cU39biU50Yi<(7!%2@)$7l>)o-Mj+#-NrAyu-W26GHJ8wgIHV9SRjb7HKrZz7 zJ!ELw(_6;x0!+qkd9SWI9wo@4)%&wnE}G6e8@vC{{=oPH5SCP1cB|<+-fRRns_9rS zn}^6~Xy(;hNWAAuQ85^cR|%*{h3kFS;>IWgH@jW`GM8$@eB7^=y|itj5f=5jW?VN<-%Tz2 zJA6l5IW6y2lA*cXNIN(ktiHqgk?v{ESRUpKFZFc$G4XJ}?KOPsYcEdF(zulA<_J4r zzn?_(kCB_VRKb(uVW`fUR%JfivLt1~a!ib&a{053h9H&i2dMx3ZPuxCQx}WBk>1CW zV1oy?aX@`Gfo~TJ(-@3B)ZAEAf1@myLJO$&2$qUF&X*pR0roqcjyo`D2=1;C`6ar& zZ&A||wqZ@k!SWmjDYC)E{acq5xbyQ}tQ|NvsX?Q3P_YCQ4do;G){LgI-_OPjNRj3M z3>wvJXdkBEQV!UF)oHpUb}!r_2~BhngxRHI087(rXS4B`5=Q((zuol6s&j6GN!0l` zG3D_QG{{v5(&bp>RPnktuq8YCoW3@zy%7P4il zRQ&XbSzz&zoaN$@z)6s!@$D!bOvf_g?wjZnZ;iRLe^0y>bEcl0I%u=k-Z5DEw;#sF zyJMR-33O3SSLl(03yl?8ZCZ-=)1ioC+OyFY6at(*cej3hb(chn8M}b5=>;hyLJ7i^ ze16fzU&2C9yD=grWF|aZabqSnfb{9=v&h2sj@@q%C-1nv-=28x{w{JmY{Z0!XJy!B zZ7#}@<`c4eUL;9c&gFn?N=@2~!QyG&z*#O&@>l3tX2g3!LuMEh%TL88;f|Eb$FG_%8*xyc0$+Y~$%RY*+;wWSV!`C&)Ny`8u1+M~AJNU?ZQF{%mW z2w3X%L$dpSx6737fVOtdWieT)0MjpS#}>OOE8k9{mL(R-Cxa%M0)Js#_HEaan9PaW zBOS5tzr9!jDbKo>dRjK3?rQ(H7q$>!b|Izl3HoDr>CB&y&BytkqH5bCjX0aCqZI z1tnG$F0hM}cW&YjU`x~Yoak>G5uf*nt=?6bcPOJYrRw=%PWm11(Y4fU+Gl-4>|adl z@7PN(>XSD+DQS7yVqFybhoYh4wkfv|KVMI^;-D;;w`J6)Yww%Uc_PBSBv9FUgjK)G(mheC;e^(dR$~^lOLi*Vu zp=$xA9C5=CkQO`4Z`W9~pSV)}Ae57C*+9S26gG8bv~r=gh3!w%;u(W5#}{L+W3>A& z7*|&9>-eA)uTAdc&FK=PxC~$WQ>>{Z!VU58KIABc@+wyRGdYk@`r%>qdt~$*pq_~_ zlYYw&`h`DZrEhyqFv7uBhuKg%uBe%wQuS>6BbBPUAX$(TvPsu;;)#s-d#k|*uIY?i z#mOHwwP2v-6Ht{_dvCev6D9!LjF5h{CzW9_NK6Sram1{n=Pq=XqiU=+iBc|1JWTD! zPCQk!MXR)Ur+wYB->lh8@d<3nLA1+~+2O+2Fk)>Nfspm?U3Ixx5*N3?;z)N(R0yq4AbXo2 zS&yPl{YJHMAwS+Q?)7qR;3(wSFNAzSU~O|Y$UrNgo*zO>b|b(`A|U11g(M^)^E0Vc z9n76@p{pvQg%oxu%6Z&?UpwO}shYb9_cswYH!^V&YE+6*PQgwr%ad08y!PkRa>Wxh zC8Zy^IM|FYle7b(06S!~?osO-cX1PrMzm;$Yz^-0adraRoavchmka-G!M96>5g)8; z1*&Be;x(STP?D!=HSAuM9In?o(JV_wxNHN`(dU~eT!-|pN*K}cI+{i6e005$K-OS#*iOm2@A!q$TjdMM*@y(y3c5E}sNfvT62i|g(E+gB4tbh4kRTdpoMiw$ zQQM-(9~!h3$t8=deow>S^-|rA_~QOP7+j{1C+!Q#{dn{T2z&l8)mH5+7;ORryD6%c zH%oFv)HFd%Fj5QVkgDIZ7#gIg422xZ?EpbnEP)+H$+MNLU1WRWP#QCrES3967677x zLDM$}w{~nBbm;Lh-JGAc2)V$SRK;cpe7q!Zgk-6@BA0K0i^GJqmTIwmkWsQ43bLIT zOAuR#?w-8oaJ-8_f_8tSM%3NDobd6D&(cFq=sagXAC~AYj1`1o*VSVso}8buI_CId z_Q@6S?oStHQ+(SGKDa@dvFHjQZr;-GyH;M{xn_BQkusO9V5bP{#8g)<>Fta18enc` z95*UhUfgEImVNreXZS-U2cE2!z4Hrf|7dITNIW+sq26svX1L&V+qu+%lhjM#eF)$5 zl*EG?5Lf-MDaf6I6PZyHr!W3Z&V{_D3Glj#wpt;UcTaeqHxGKJ7#Iao7LV^#L~F;% zB2Q3ezXuu=ym{&Y8XRzE^ZjE@p66oHk7W~#4#UqYdmcgXp4($^N1~5M87j>jXwA5XJnyBHOd5{#V#lOiS*%Ur8%!QB@al zUk6s>-Twf@L49ROYIr9n)$ZVNA>Kqrzj?RJTX76XD%}6zu@uq)%H5kqA+4$@cdB(Z zGJSK8$Oly@0Zl8kZ1rAHZx)mOB!mBO7kbkWTWA8xN_!N%d^CK4!LUE4?{e^f^&{$n zp{8-MbeY{q@nyvr>j^KM@=)6JMiH@enR{fePfuazMK)}kW~q1ks)owB6~;SaZ?a<) ziW?&4fp^oj1_khlHTpjM>Wh2P8`tk63jbvxx&Z$q zJsE%GzkcfHUx&9iDC#&@Cn~T=tQh{4rqAUu^-+s|0}HZINl%n55pxvT&MfOo38OMm zI(_h?ihzIok`c&-ZaEiKF!OO3a{%m^OgT~7b2%)s>p_cKWHq`a0>)u?wbsk8xN5J->rYiT@Fp-xGFU&_g5Kd9Gj(An zv{r2*eGbYv6<4UX5d1iCT**e2);AiuNYf3&#iv|RVJ|IY(F_DP9u^hUD0?lazHZu< zQTb-R34V6hzDHD9p9TyF7QcgMohmW`0C2E{?NA*ezr(9P;&)+_{@jN275QVY6z+3J z8HX)0adr8f$*)r`FT$)DJ@Y|+Of`rHld zcO5&f9g{J)%pz!Fj>Xv>o(>N2FJ40(l=SQHEjc~JUC!QTt{T;@DM|_`9CO%?*1%`m z==29brs*@|7ggKVFb6ytGbbqFpeT*8#>&b4N>`v~B7ly*Hu+vGHW*IxPaQO+Kn=x4osI(a^|hF9u5#e&BUEc%KVTg+pJpV4K=Y8tM-=lcuaBD;eZU z#igS-i!BZyc8YWo8)&z#Ji!B@F5C5%;bu|IY_D5p>tpq_-^4#Uuo(Q1rcqCR>sJM4 ztJ91uwgq7UCPG}HkL#zzm>$!0nJvh4WJl7H_f+ zPH0&Q59#sSEx5OSqI1ox(kWC(dii+r{{Be5cAgRPeUcVl!S`>&^XWf~^wOJc{cftg zpKo0f?*|pEI$6jV`x5$2-+DIq3P7|%by67Qblw|MMa&GwF94z)wQj^b1O~Kp7)(yB zk9V}Sxq?PhLyfIsi-^%3okYS><$x3ZB%m!x^3_7L-mXy+rC8aQg8~Tej+6Y6=MnaQ z#(I9(D1FYqhBkr?fsff<4lK6T(MHcIN|t^r-Wpp#_%7aUFP9{FTm?RO-<7tC=Mx?6 ztUDk+Nt6=0D~`4X(OECSQ5IT0jwMF407J`!!NY8()c`esiwnHC{|$mW z{SF0gYYujh+e%F0ZQS@*_dGSyKLs2ST8n@p@6<4PlYS(f;vKmqehd~%Jl@+SkOdPC zU_{iuij9h|+0W*0{0?z&dLq6k?ybQ;@jjYcs zjxhM*C^48fP z>lJW+A6}VJDYB>~-V3Pw=joJm$87s~iyVzM#{J5+1e*W6wvEt~`BC#b$qmkLOEp|u zGND}_tOU<3jF`q!GGHIVov_FGfU2P({h6+xW3#NbtT0wqip!#d4ShC7iQ<3_rY<_z zxQJ%Mma$wAzbmoya7L|(x?=6GiuRN^QZ})n6Sfp{uf!r8Bl{GA5J4y@kxwGHo0h_<54Jf+Vs3b={ zMt)@++zh~mPIExomIgzXL8LzIw)-!xHL-GL-nF)mgj!*%x;P0eF{;G3;`O*0qtWt! z{aV4TtXCPhw*)*yTZct_Y`R3vqs#38-yBSX*uiZw-^?)h3YV15vJt)|SiLWFlHL#= z(@u+VP4?NM9aP2ns)f^B%G0r~od7J@B-?!pmqIo0z>e>$HWsTu{z0ZI6S{yW zgt5wWLgj`~(m|BFE}5~k1Rq5Vh1KVJ9o)?pd!BP?F^>`~Z2P{nT5JN1d@5v!J7u-P zIAI38VHoCHqO-?{(UM=3AG=M=t;%MmYosj$`4IXPVLr!o43nru4UbF|=dgs5-M3Dm4CLUIM=Tx4 zs&S`=R4{EQg&4`5r@Ii05dQrgB6_#BbIMkfkO^cqy(7EorFS0B zUg%}xcY>$?ko{gnb$>w1V(rd&)03Zm7PFmTL*;dF+(0=ZOM0&*$%eWLP4klP9 z8c?hEnlIQjrUlaBMfT(aU6?P2ei*;?)g1!A@`opX008WK+EIBMJu5kVpXhplV9euv zj5jWQZbL|;H)Y1_8WWmFwa;Ckk|#XCsywTiD^PbPl1X%A5=p#G&9))h2KFSR!0|0B zqX12|=-Xr}o!gNg?ML1>v-JdYK!ztEI$!{bfNIM;;w)4lG!&B1wAHmUN{cj65~l2p zMqec3gl<>fi@0Ll>Sk!)Iwhw2wFw~+K6V7)^D#|GBYY0@O|_S0j4l!cfKf=*sD$IP zWoama3SC=Erb0NzjQOh6R`9&-YdEFDTeLAsyH@e#=S8%t6(P(PG#h+@$hE^*aq{xR zS2JXg7yv1H^(YWpSho5r^JenNmEA)PV%34*fHZ98$Hi3dID~}z@2L8LF+zu}txqUX z0pDldlfBV-_d`q1+ys+$VyE1hvtK@7j|ONeNrFUA*`VSdd=Ru5V1xD?v@~Z)tGTQ{ z+%dK50}#j3hOXn1+}c?TQ+wnf3qkQeW-${Na4sA~&#=gQNJ!q^SJL+W(;vZ5~Co zatL_Dh&Bj2Tr_9P5PZ9tz^)D)G(d|l{Jro!SB>_Z2GnLa%~Mr^_u@apZe#IFMYLSAjBsyK?3pp;FLR*}z7`r!@zcB6Vq-WK~|;AIzJ zqyhF(iocLL{pH>-907kW#slT!eOi|cukL14r;j~)Os{ejyH>eb;7JOuo}if=djExU zd+=(gjdZ(W3HEggwKCv@y8nT9c5rI|&toDe>|*W__eWDZn-y&dwnj1ChrD$}T_-E~ z;4tLgL+x%W{X(}jEW_bws=w40O91jpw@}k5A`Y=s+ zEbS<~Y$iHmo$-!P=kbms0~jf#H4EccFID`)r_}5i zNUSN|FN>7iIJJ?fWM-)=iG_i8mEcs*SCz44VykqxF5f>ay@dKkSlX9c1Iim>e5Eqf zvnng{<|}_hBVH^+xgxy~mqNodw~1Fd-345Hh6b2wmA`v5=Hsu(3I5l%Z-CrM?}rUF zT!^@f&kve)0*V)Tq!IW^IV? zvMJECoT;LvQE_&9SU})SwQxP{x;SRjcHw?E83B)Z->yBKbsRon`&zaxNBgX?@<9^1 z^E|e1iaFakr~-A8BE~*BtuNm%SL9thEN6xATsagqx)}E8PsD;)1ZmC-G0=ZRfUj))%Eg#BM4_`J4^PtI31r6 zjEr4I#L4LQ=sELg)2eqmLhgCp=rqR91D+spHK$ebLp%7+YcGg2mtxCUVe$X&B8Y^s zPvfAPEY*Y69B%14dTQoEIbc_c&zD(S0t1n^L6~{db{j%zZ74F$h)_cXiLLjd>Jc{4!S~)Dkn{=b2HM7Gp!=kAA5SMtYa~*qujrb=Ww3 zB8X^BD%=!eE5?m0>uR2@@srx?fo-Zgd~Dc2nJOn%@#=vvK`yV9m%5ZfNF#O_gGPVQ z0{<~yZQ`lTnYEQNJmuEs-Vj(kHmsI)LBN|cP>{|5=0T?q*J*3TY0{L_NX=*30(rJ* z&zLzC|5=Tfqa{ZZc7J+iL}IH#CPhJpqFg5FP?n2z;54>Xs>vjnTBa=L3U12U^)Mma zA-*H)6FG=4Lcafkr@7xm`wsxli0NdObV6hbjNrt7?_{vke0G59ZN1TgPhTDLD}ue$ zHWtbsHT#k-1f>%ckzP&;=#$_bWjnC@fn4lK`j+U7veMA1`w{Cl7w~#%2){$#=ZB^W z4`=AS(Vfe?!ZslV!D@u*Nm;HzGI?!7#L+CMD}1l#^j1-IW#uylS*M31Rf6uvm_fkh zh;*N%dOa?%SZn6EpVZ2j7y1= zC6Is*o6j%&$vgPqjnqjeV zb{+#7M7ZtCF4inaJ3Cb|2j6o^w^vgPE`L+tBz_`d$gFRz3~O$Vc=Y0DzRe#1>M=Ae zRSQaW6T%eE*uDXvP-$!f6p1lZ9UIzRCm*8%ud<%&pBs-GM%V5rc62a%+9BiF46u`ZBevx{BhB zo(jkmS_@%!wx+%~@{?Eexx{U=z;;@{0*a(f-?xTQRw7AdLXhih-u@mx)F*gy+Ek!j zMCyLkvT^8Ub=1IDaStn>f^0>D>kDVLVCpkk(fgNSD#460CvbTI+idtz)=RpSb{d+> zrLa={3_}DYb-pXXe9yu^%s>cTSO=0W{Wd{jlO5CHM7nM)f5>CWiv20Ibsf`QzP9nl zs|_>S{R~{yB5imBCLxrwl_4hx2_V)x;)W|wj z^FWF1CXkL^@Y9Rjz`XDRkIDt?nn)w_Vzl`3z?q2D9p`6+&jD?wOM8dXMnY?wnhWmlOJ}ERWHL4+Nj*uDqXhbX%(*Ne z=<83Haded7cgQXOD4lVmaf8Z(wAH5LDl$b&5mBn0K3ieN+i06%o6(_@3o>Mq-3N1I zUd5&;ixnOTAsizUq$jg%*{YK^k@~NyQPctC4LDTdN6-LOS7Fi)it!OvZOwaPbN{*O zy!D`!@xk^ej|_X9fOGP7as!^)PUMIsI2<+1*wQ+5&UO<8YXRYC{hJy`52ryh_=@Q| zwQ?P=$7+lJWcoXyH?cVc)y5WYHCUP*<98Y<@=!*TV}jd};z!wko(owt1WwkpgrDoeBeQm8oJa~PpJ z!qUi?apzWp6{{#nXf{aC<~@GCjujrHmn%rh2XinX@_wFU=y4V9Q;=PT68p+%XC9)$ z1oIue`=YZC9nJpO1A_bMrk4~m?AWBlMJ4EF+x8Ww)uBU3ZG)}pV8q_a*z`oW&Ge2K zkQ8=mUtdZT17SP%29!Aqxax91C^;6+lX)(eLt-$%_H2;6R~)Ixf#Z7)KTgMEg*7x+ zFgOxY5qvhT4hg2g9MV1mAwIGQ8PWpNstXVGt?mzLP>em;B83cKX#jQNo&B9%dN&RRsHuh3WyCGlBnOY!v+SG2jattjzqCf9 zfRaaRJPaA?xbL|Uf3y|j0+$3elkX4pqfkR6ZcYP@KDDzr{$5Bq%|@dml0?Y{f^AIR zUU<>l`le@CqYRR96^7psBPZF@rmz2=kP$o~DSMUa-4lhdq=iZ~1)S61T%*-u^6P0W zMWhBpm1r8+!NNlv;J2OsAj}gbCT|*KA2KxI2xITG;^_r=S_sW*P-R%jmKn7b^@!-x zl))kq3Jn*{s2@1yEw%E&WD)JpmJFm1W<=Ij-)bIbmFEa`V>ZoMHNeDFmDTH|lMEL0 zVrEmVMRNK%zz4;35~8Jj za5QDQArtv}?niq3KQPGUF4Me+ih_=Ro&`x1-H&=9PP7jVVuE@nYZQ4}$g-7pBGqUD z6$_l~7ujWVE6E}8DLs{ z>NWaxj3PS|_s;5W_ul0%QK~D%2w0Goj=S&(+Y(!gPoxWFw)6%=KOhW( zky)V5YZ$#YL*ZbQQ?XE3!G|Lir_cSSbosPsmP`P2&^fPVq*cDsQ7NW|4e2&^NUwh| z$n=2+l2}?1E0Th}v8wHvkBJz&qofmL*R#y?1t}u-MCD)=jAu!(X1vELD!8|5rO=TxD#DLY{ms?lj5_NXRbe81Y2P$j5lLiu#EZ;s`ePn zFk`E@G4DH|=?>Nr$Err5Tv584{jEGKWw2r?@h8r{x4qtx8RuC!*N-R?TAhQIuf?}|lB>;GP}1D85d z!9qB-GiYy9j?#aAHent->;i|```V&b8<57M2iQ`Mu_{YW^@cnm*4q8`aMIhgSBVt|IB~*->uPq zdVc5Q!X}Ho+0PY+NdOgD`Qk5f)5k&Z&_7Nk{gLo=N>bBP$Q?_T5RG+wtA!%V%oi^) zhc|2BotI#rtS*$LUOrEW)xb<2PPt!gi=ZeRhoSKocv@58pWKkEC*kG+3pa)IWS{(v z;UxcAap#7fpA@QBGFJ{FOZs8y3$Rh*d5S&p-w>r;IvD0Qlhn;!e9vyAYz;W)1oT7U;GpVOU?7FUoG}mSH9gcN@wQ^`tg^R+@bUKcW z;Z0;ro5M1|?sU$8C{kl-Fwschcdg!Wph>(rD$%Jj29t&sm5EG3R%8}RMeh2GXC~i6 z@BUpmT9@LWLWpkIDGkD)km0A<`@nN0<0UhF$#wBXT#upI8R(wknxSZ)Jn>6CN5)fekm^=YoM?mSt#yX8j-`;A#F} ztDs$XtuMp#^@(?V6FpsxE?-I+}mW?1B1s)A+3}` zko4lNrcFM3{NPmyt#z)S2a68H1+b0;RlZk!#&)7xaapszF}xD)OAs%Jd(2xZyE3>? zsDaAL)-n>u-G-dKeLVK!hZqmRl!m_StN?F1*YEd?FDyN?x>0#W|81gB^KPrWI7tTI z6Vu{GZOvnI#g-*Hga|1bhNSL&oaGB!?#d7v3pbDI9jx5>xT~IPtS(4Va`g;w-C0Ot zgz+pParR%sT*A`mT5VhXD#!e^a_OtmycP{BNXs%(fCjFe51IBm2yk8eBAmLeCqmNDu>C4VqCdv!xx&^%8}sda#3ncD zwkU7nB*~p-TnYbdIn~NSD?Dr_Gk}^bLiETfwj1>oV4paN;Q?MNTDV#x1_NfQ(tANY zM-HACY8}Dw)l>Elj_I~rbGm`*Hr&BS9MHt=ZrRPxz^bTg2dCYK$n4409N4HAJcXm0Gq&i{3 zxU@w^dZfe*I2xQ_xNo{E>Cz*O1gPfr52;(@AR_E-!j*5El1^+PHvU`&E$H<(&_jW9 z)=Ejry9xC~w;6nF+E5*~2$jabwu}N#-riIYE&BqWbVMiKUX8(wp$s&C`o(rU^k0|U zzHj$+HfrqE*Up8QnT4lHwFi=0ZXZd%nKXA&`Le9K#z+b5vYMlP$8#Nvi&Ki!6~Cr< zZ1#LP9;-9M&1=4&-ZW;&V*6p~M7&_6Ql9ELt?>1ME-Rr!tm?_s@&IX$eUc7MhbyHq zST>0QcB+ksU#@-E@6Ex2LcmYv1fGb~MDpHW{fWLd3y^*}v!aC(XZ<}#s zP>1I1Uvu>`d-KsOqru?kZlfHXL@&;C_1;jNcpIr0RwuLWFsn`#1Se$6k-6`*cg3`N zc|puZe!|HG2(F$EzDVR%Mq4=F{(ZVSd?o^Ykl>+ugKm;HbUOV~8v=6*s(bofUI~i| zQKoR|aLf0aSayrA9f-`=A&a8mloUB69X{l6IwZ<^y#AZm?x|J;Ebc>o#gGBJ4XKmq zu!fxB`Z0ypbQR*}TbjDEmUVpAs0HYQs*!?EfFH?5<@2JjZrs@(9cOdvUo@}G4#)&B zD3%BTVX#lsgdaP+W>Z0UpmOO*mj6fc=J)zxfyu4U#eVOUCqF{Cd<)6|sC+WTm8!fU zF6!Eh-^-Sqs5?K=+c$cHN)yEoBsEx&cSbfs=7x_9Ca>HJOJ9HUXLGNUUW|Sp>Ip$? zghz6cgbFtt&b|{P;)CtS(LBNhr0(&oZVWhW1I{J0Gc>{^2Jn&8h<9Ek+v_6Dk*(6a zRA1)7ed3EJu1QEPknqQ3CuNF$tHfs`M6Dj&J(GrGD}qEYmN7Z+ZguQ+QI|6}@XZPP zD}!K2bmCdQVvG2c>w2*^KmbSv3e5`dMJM=0ztJ)wWpHWs@dSGGXG178(NT3T~ zz7?~Jp>O|6UphgSm2BH(CAXF&EMNULF*Bj5lDYA4nzTo;_7SXif&Aw8Pa@Rw2r$Yx zwkgS4QmH38yKFSfP-i3J^}U}s{_{q?NhgJK=55YXo;@E&evI3@;oqW4LV+!9^wD@; zln;fSFK;5l0;+S4x=k%kQ?QCt6aCh7*K&aUWC(qcrDN+vT81$34geg}yWp4I6rpH` z^TBAwr{x{W?Igp}`~lD(z_FQKa^+djc9msFwcr%9BqId$aOry8Zww2F z%RuDdbJ`&l$%(#{hLP5APLgvYexllU2pIzo;tN z|NZt8X5gTf@?`UhC&H*)ni-a|)cfGUCC{s7PAVsUYQGn{K@^mQ*D!HzwtqnPSe_10 zAFV>Z{08;j)<-xz)Zx8!uDp*)`w?k-yf@L%&XFB#5w#||J+kz75Mg^18s)lugG}Y2xjSY{#T9mo=&1Npt@^x;;oU8B` z>YM}sOi;g(w3%$=2P=vsQwk=*DaQmRfSH|<+%%9fpwT!HUs_I_8Z?%3f)e1$E#k<2 zv04&vR-62VY*{7<0>aOe;&8f-x591Ey(C=sWle+KNT$`1Pg~&WR!p|b=54d#ifPc& zRay)P1V$@heXmzaq!ckjx%N_VNces~L*E7>$|%+7gZbXgn4IF1@XHTe?`Kv%Up_aW|1LR3(C`Mbld_&svUK+a|aur9?-Y z#3@}JD*J7o2l;r^x@OE?(BP}FI$_8w~c_pL0Ht`A25e_%DbH5^|^V}5kOw|y}PziKBU-)ne%qy)1kzzk% z>5ww>|1tL-a8V@71MqAB7Fb{j;*w;^8I~MGSaQY%L_tt8A|gQ$P*Ikgk(^PI1VtsO z;Hl)CB*=lNWCcY@3QF>u1yS$r-96v;{_p#J!_ZUHJyTU()m>fHGuu7EACl0sK_QB~2sqJ1sb+4|?MO01}q_^KheNU+`K0bz>2qZ!fEKFC(7m99#z{ z&c78OrQXBWCxjo1r1nVT7yW7Vq*)80F$BO13(BItmA%yiE-+nZ>r-WA4_XwKYCFnk zEe2B8IPshD1=GTTt}oQ|0>qD~j8sWZusk*=c zyEY;jF3Y@U(3y6->4D^n8O#acC>Z7)ja6e(MOc=^@z!Zbsna{kehj^t8>Y!&XG+Vm z{}mOpu>RqCd85N8p#O6TQ+%mJLrbs`=+bvM<6UFZQO^r_Nm)NPCP@X05Aai@rUE9I&$)z z89}6&b(*u3DfhsrhVJBPx7mQir|I=3vRY?f?jPB)nlb?YR*EA(yM|jXMB9lFEUCsx ziX(+E?{g09;biCoz!}~&bKcK~%(ZXP`I3&*#v=txYsy&Toa>q_`Dcqe zv2x~X{Z3b3Su*h$8AOBAGt7-(xBO)1(xeeSI2~-CgCwhQLB@~)Hv=Fj_LrTz5sKIq z`#ZMswpp}{`0)f3uSdi3bnfdMCK#V=1kDh?QrvJNDXwjW97vr5Dlu{1;~@}Vz=-uw zBAz@T5Q*auhAQg907xwOIk(^$`cImZm=*-7xqRmP{q-#^=HeCFXfc+^cIc2bA~1Hr zJAyWC(%K6MrGvwQQ*a|BGmjAbp@0NW_cL->7m9<18i{L^T0KTTPT)pV^E_Rgetqg~ zSe<$n7oZX^=6Y)%88;6lPgh&O99o(Rx(Fw@Dej*fT&$LjEo%Qg&WeBBn$?xISWfm+vBO~0?y|5GD-kcc|;jEz=fi{}q4 z1|NKa?h$LSd{Bpj;xUtHu?kDADlA^0eJGb{%`hGdNImQ=tR^o2gty_shE01{y!0dz zxsX^!F5d$Lw!Z1wCzVO+}2a)BjMMkjmw}+RZm-_z4Y5QXF)^+ zy4qB2w0d2G9R{}s*FsVPvM`%*@8B#N%rH;H6rq%jLaw6qsr5`B4LETK#!5c8Ja50J zXVPz7H7&60=rnF$Uxz`x$g@{u>dqMiu_m5S-GA5^;J%Foo5(8lUmiK3$ zv^}$z!?aW2$l0pN%2l~8Y==0l9=+=DpC=br)u^4px9*R~(M`QSc3~C!rJ(^+A2b#7 z=CNbRSgjG6oqSj-25*RmjmYx?fZdfO!#*;k(XclK+yZ%(@rzsYN8 zrM`jC5#WZ^Xo->6EdrMc!=D4zOlW87$jj@_!skCh%Lg8I$tMbVEe^{88fa`dKqJ?v zjugTfupS8(m!=7(Veo#g-)=(42_P8y?R)lq8f{rqr^du2C}orh$DK>1MDstDzlzcS ztxAO3Sthnm`393gk~>>gtx_^+kzszdx=r838$9tzD$qdWFqoP|zVvgHS2S zMZ(XoVc%vXeu~kEo;I*9Ya>P8NBY|jOb?aB=lKQ`G*@h7-;9;~2tF7)))0kLtZwh` zkDe`qQjl7wypCFSymiqlzEt6f#gKFM=Fl)n+-$ulr(|A|5;uuT-5PGP5qre^DJ?m# zc_}%`a{&*d5PCQS8=n<7E>omM#;uiO9!gEENDJW*Nn|$4H&TerU)qqD5MVNerT{R63=;R)nDq`Prl8b zeZS#jEV*^!nu}+*q^V;tZVHYLIY_FRW!HN|0R`i;p_;Q50l(H4d(BCkO)m!{erigK z6SlEtm?^TM%7>kw&!=dCMdhy_yHvp;N!lD22QHV~NE<7+9ehr^I4j8{&=9g*=Evw{ zD#)~``KFpb`Rur7U}{>>mz?YM5#UOMPdM6*hChroaiHtQxK--LFA@T1>gBi}j_E%yO z>wETcPI&LQ$&lxbri14{xqhFx!8wiuWvBJTF0S%)4hdcq=_Ua3Q@zvAOB*-?LFD}wbpWE71P=i=e|YGDH}`2?P4k{i$TvIXW+pjAT` zoeJ2s3mkJFXPrDDRZi`RGU2`l{ZiQ29W49|y+5XlIXsp{3qQf59LgWSq#!4Jf|bh- zGnu+iIoz5A53JYW14=n99C4~uQs#;%tpP?SJLo57QpWd{#YwJr|Br+1ZW7B@p9R zOmDVUbH^EJ50H ImvP_+ZU;XywzU$mK8eixfly!b$TGsvRa*U-zDY0cnu&h+4>UH1d~jm1w0s7+sLzj&hRy-iyOMW9{=b&*#Lv zO^43f>TlDW{2sJ^cFbqGx$g7%GT%t~XIg@-W#ZEn@heuxl;{jGm z%_u3To9tMHPiV0!4yLND=IUBLQejjX8j>ZoUi!k$%QXog9LEXG@-v!y#V+1I{b>yG zF+P3x+?UjQt9RR%Rngj{FT%oA>x%kzR<*N1lLDF_HseaA8{;sS@k+ptvUclU6P;~yFsdYsTKOk@ROI>41pDnI{#+h z+)a_iVq2!1&jw^iG4;)42kOe&Nlr*;jlwi81~U8F_ztSvhea8CL_R*|5QVM@v`2prKIy}aw%NFDQgyDOm$zoqInl8^y(2$8 zA43kgCkEr`?ny0WOUmED0W@IKkLf>)3_ocf$;@Je%99oD zh3walhdZHt=}iJu)g=S1^ZlM!%auor-!&a^PnHbmXi*+i=4`Xi<27Yd&uY@BJKqF5 z+z@_1pMoahqabSyO$_b&+gI|WS{DwCtdkY6DqoUdp-0=rMJpY3#c`3uXlk+^M?P{h zk|1f|%OkEgn_oEIpwf^X#pqV0C?U?OcfIO|e%Nlgqm}VWSDV4#$P@_FiDE-&snPkO zGg69ijajeBxvfvoE1tubJrF^C1&X`WVFhf^l{+g1(FN;LmWT7Oq7fPLIn3UWdW}is zrmAUJSaV;Z?*%durA(4&7-fK2`r8y0Tpg{aFoTP5SZtBAT34U9H|+j0few6Cv;V0U zv@y=6^TGr9bLbd+sCL*#NXzm;_64a!VOF?Ce4zf**qL*y$HW}|zfbHjv3Qz!I8*gn zeHI*RSEE-wV+hX;PX7uRa;Nbjx!X#MD~ZbwjQz|0yK_+kMd8V z9&z}pE{04{HV~ie|Kcg49;p7vz)xz;iV7zf-gLo@d=A8mWw+Q_HJp0qW4^5F>rOHm z)x1AajNv5zBO~9uC5^v7g}K*oFxeME?y>1Qv!y|BScF_ya|Bz}ymnmQ#vD7T+$FUl zz-smh*(PLK3$~A~2VogxgE<7QK#Yyj-+G%#d^27HZKU+kE(cHZdO!m zf;i$6sUOXfqBbT_9ES2l`>DxYtzLV)aOu_eq^?aAW;0+d0_mBjWk5D)5pq6J=moMJ zXB~3kvHNnpCyoq5qAL<1HGw}h0~skp!3MzfR7f$C|Mh~rNno_ZP5L?R3!*v$t#)Dc z@T$TsNiDkW{3|?20|47#HeCXcoGspivDch{_IU&&&O5n(aV4U+;RMocMaw~tx zQ`>$Ae*6*%1AjOQPA7oBL;ZIPT>sk@u>Us~AUFPJjI}+pgZ}<+mi7LBS*hi#m<(2{ z{|VeG2?8CLK3(l^45Qe9M;7x(ShL#LV|b_C>U}L{suYegs~=sQkS;AmB%=(Zc1mXQ#jM_7zZEX|H{AccpXl(C?dG121Jp{T4^x zE3MqFSTXQFwNrd$mtgPR>J6&R#s&X{&FU%K`o=fj!FnjtjDZX2oYlIn(QE}0A#%Af zcKOqBqu{|an^oXG4U-1rLk_jWC>S#Es=#8z9QSK43g z7H!>CT&dey8(s2=zx+1q5=en4G@|a#dVgyhATPChc>a^)&`LeX#((zr;P5vc`iz~e z`u!&ze&glx8mOcP{~6A`tJgM{-1gvKJ%0Aw(f7CZLu3LJ4pHCVb3iF86S1+hvU>b+ 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 z�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 is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n", + " warnings.warn(message, PydanticJsonSchemaWarning)\n" + ] + } + ], + "source": [ + "pprint(KItem.model_json_schema())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate the KTypes defined in the remote instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KTypes.Organization\n", + "KTypes.Expert\n", + "KTypes.App\n", + "KTypes.DatasetCatalog\n", + "KTypes.TestSeries\n", + "KTypes.TestMatthias\n", + "KTypes.Characterizationprocedure\n", + "KTypes.Dataset\n", + "KTypes.Specimen\n", + "KTypes.Formtest2\n" + ] + }, + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", + "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", + "\u001b[1;31mClick here for more info. \n", + "\u001b[1;31mView Jupyter log for further details." + ] + } + ], + "source": [ + "for ktype in dsms.ktypes:\n", + " print(ktype)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb new file mode 100644 index 0000000..8c26bd5 --- /dev/null +++ b/docs/source/tutorials/2_creation.ipynb @@ -0,0 +1,191 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Create Kitems\n", + "\n", + "In this tutorial we see how to create new Kitems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1: Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2: Create KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can make new KItems by simple class-initiation: (Make Sure existing KItems is not put as input). \n", + "#" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'KItem' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m item \u001b[38;5;241m=\u001b[39m \u001b[43mKItem\u001b[49m(\n\u001b[0;32m 2\u001b[0m name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfoo123\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 3\u001b[0m ktype_id\u001b[38;5;241m=\u001b[39mdsms\u001b[38;5;241m.\u001b[39mktypes\u001b[38;5;241m.\u001b[39mDatasetCatalog,\n\u001b[0;32m 4\u001b[0m custom_properties\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfoo\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbar\u001b[39m\u001b[38;5;124m\"\u001b[39m},\n\u001b[0;32m 5\u001b[0m )\n\u001b[0;32m 7\u001b[0m item\n", + "\u001b[1;31mNameError\u001b[0m: name 'KItem' is not defined" + ] + }, + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", + "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", + "\u001b[1;31mClick here for more info. \n", + "\u001b[1;31mView Jupyter log for further details." + ] + } + ], + "source": [ + "item = KItem(\n", + " name=\"foo123\",\n", + " ktype_id=dsms.ktypes.DatasetCatalog,\n", + " custom_properties={\"foo\": \"bar\"},\n", + ")\n", + "\n", + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Remember: changes are only syncronized with the DSMS when you call the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the object we created before running the `commit`-method has automatically been updated, e.g. with the creation- and update-timestamp:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And the list of our KItems in the DSMS has been updated as well:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/tutorials/3_updation.ipynb b/docs/source/tutorials/3_updation.ipynb new file mode 100644 index 0000000..3089a76 --- /dev/null +++ b/docs/source/tutorials/3_updation.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Updating KItems\n", + "\n", + "In this tutorial we see how to update existing Kitems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.1. Setting up\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. Updating Kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we would like to update the properties of our KItem we created previously.\n", + "\n", + "Depending on the schema of each property (see `KItem.model_schema_json()` in the **Introduction** of this tutorial), we can simply use the standard `list`-method as we know them from basic Python (e.g. for the `annotations`, `attachments`, `external_link`, etc). \n", + "\n", + "\n", + "Other properties which are not `list`-like can be simply set by attribute-assignment (e.g. `name`, `slug`, `ktype_id`, etc)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# specify the path to any arbitrary file to be uploaded\n", + "file = os.path.join(\"..\", \"README.md\")\n", + "\n", + "item.name = \"foobar\"\n", + "item.custom_properties.update({\"foobar\": \"foobar\"})\n", + "item.attachments.append({\"name\": file})\n", + "item.annotations.append(\n", + " {\n", + " \"iri\": \"www.example.org/foo\",\n", + " \"name\": \"example class\",\n", + " \"namespace\": \"www.example.org\",\n", + " }\n", + ")\n", + "item.external_links.append(\n", + " {\"url\": \"http://example.org\", \"label\": \"example link\"}\n", + ")\n", + "item.contacts.append({\"name\": \"foo\", \"email\": \"foo@bar.mail\"})\n", + "item.affiliations.append({\"name\": \"foobar team\"})\n", + "item.user_groups.append({\"name\": \"foogroup\", \"group_id\": \"123\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Changes are sent to the DSMS through the `commit`-method again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see now that e.g. the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have beem thrown during the `commit`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Furthermore we can also download the file we uploaded again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for file in item.attachments:\n", + " download = file.download()\n", + "\n", + " print(\"\\t\\t\\t Downloaded file:\", file)\n", + " print(\"|------------------------------------Beginning of file------------------------------------|\")\n", + " print(download)\n", + " print(\"|---------------------------------------End of file---------------------------------------|\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "0.0.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/tutorials/4_deletion.ipynb b/docs/source/tutorials/4_deletion.ipynb new file mode 100644 index 0000000..7023487 --- /dev/null +++ b/docs/source/tutorials/4_deletion.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Deleting KItems\n", + "\n", + "In this tutorial we see how to delete new Kitems and their properties." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.1. Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2. Deletion of KItems and their properties" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also remove properties from the KItem without deleting the KItem itself.\n", + "\n", + "For the `list`-like properties, we can use the standard `list`-methods from basic Python again (e.g. `pop`, `remove`, etc. or the `del`-operator).\n", + "\n", + "For the other, non-`list`-like properties, we can simply use the attribute-assignment again.\n", + "\n", + "When we only want single parts of the properties in the KItem, we can do it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item.attachments.pop(0)\n", + "item.annotations.pop(0)\n", + "item.external_links.pop(0)\n", + "item.contacts.pop(0)\n", + "item.user_groups.pop(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we can also reset the entire property by setting it to e.g. an empty list again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item.affiliations = []" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Send the changes to the DSMS with the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we can also delete the whole KItem from the DSMS by applying the `del`-operator to the `dsms`-object with the individual `KItem`-object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Commit the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see know, our KItem has been removed from our inventory:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "0.0.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/tutorials/5_search.ipynb b/docs/source/tutorials/5_search.ipynb new file mode 100644 index 0000000..c95300f --- /dev/null +++ b/docs/source/tutorials/5_search.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 5. Searching Kitems\n", + "\n", + "In this tutorial we see how to search existing Kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.1. Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see which kind of DSMS-object we own as a user:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.2. Searching for KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the last unit of this tutorial, we would like to search for specfic KItems we created in the DSMS.\n", + "\n", + "For this purpose, we will firstly create some KItems and apply the `search`-method on the `DSMS`-object later on in order to find them again in the DSMS.\n", + "\n", + "We also wnat to demonstrate here, that we can link KItems to each other in order to find e.g. a related item of type `DatasetCatalog`. For this strategy, we are using the `linked_kitems`-attribute and the `id` of the item which we would like to link.\n", + "\n", + "The procedure looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item = KItem(\n", + " name=\"foo 1\",\n", + " ktype_id=dsms.ktypes.DatasetCatalog\n", + ")\n", + "\n", + "item2 = KItem(\n", + " name=\"foo 2\",\n", + " ktype_id=dsms.ktypes.Organization,\n", + " linked_kitems=[item],\n", + " annotations=[\n", + " {\n", + " \"iri\": \"www.example.org/foo\",\n", + " \"name\": \"foo\",\n", + " \"namespace\": \"www.example.org\",\n", + " }\n", + " ],\n", + ")\n", + "item3 = KItem(\n", + " name=\"foo 3\", \n", + " ktype_id=dsms.ktypes.Organization\n", + ")\n", + "item4 = KItem(\n", + " name=\"foo 4\",\n", + " ktype_id=dsms.ktypes.Organization,\n", + " annotations=[\n", + " {\n", + " \"iri\": \"www.example.org/bar\",\n", + " \"name\": \"bar\",\n", + " \"namespace\": \"https://www.example.org\",\n", + " }\n", + " ],\n", + ")\n", + "\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we are apply to search for e.g. kitems of type `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` and `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... or for all of type `DatasetCatalog` with `foo` in the name:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` with the annotation `www.example.org/foo`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(\n", + " ktypes=[dsms.ktypes.Organization], annotations=[\"www.example.org/foo\"]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Clean up the DSMS from the tutortial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]\n", + "del dsms[item2]\n", + "del dsms[item3]\n", + "del dsms[item4]\n", + "\n", + "dsms.commit()\n", + "\n", + "dsms.kitems" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "0.0.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/tutorials/6_app_HDF5.ipynb b/docs/source/tutorials/6_app_HDF5.ipynb new file mode 100644 index 0000000..15bf05d --- /dev/null +++ b/docs/source/tutorials/6_app_HDF5.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6. Create Kitems\n", + "\n", + "In this tutorial we see how to :\n", + "\n", + "1.Investigate which apps are available \n", + "\n", + "2.Extract kitems into dataframes via retrieval using hdf5 format." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1: Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see which kind of DSMS-object we own as a user:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1. Investigating Available Apps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate which apps are available through JupyterLab:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.apps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2. Extraction into Data Frame for further analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are also able to upload dataframes or time series data and investigate them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", + "\n", + "\n", + "item = KItem(name=\"testdata123\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", + "dsms.commit()\n", + "\n", + "print(\"Column-wise:\")\n", + "for column in item.hdf5:\n", + " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n", + "\n", + "df = item.hdf5.to_df()\n", + "print(\"\\nAs data frame:\")\n", + "print(df)\n", + "\n", + "new_df = df.drop(['a'], axis=1)\n", + "item.hdf5 = new_df\n", + "\n", + "dsms.commit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From c2926ca227bdb1f6ddfb95dda1ad8837a76aa84f Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Tue, 16 Apr 2024 19:20:03 +0000 Subject: [PATCH 025/114] adding requirements.txt --- docs/requirements.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..d9fd575 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,13 @@ +sphinx==3.5.3 +myst-parser==0.15.2 +sphinx_rtd_theme==0.5.2 +sphinxcontrib-plantuml==0.20.1 +nbsphinx==0.8.2 +sphinx-copybutton==0.3.1 +ipython==7.22.0 +jupyter==1.0.0 +sphinx-autobuild==2021.3.14 +sphinx-panels==0.5.2 +jinja2==3.0.3 +sphinx-markdown-tables +sphinxcontrib-redoc==1.6.0 From 6f1d0f892735318bf4f28655658f45869e7caa9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 17 Apr 2024 02:10:08 +0200 Subject: [PATCH 026/114] introduce new unit semantics from data2rdf --- .../properties/custom_datatype/numerical.py | 2 +- dsms/knowledge/properties/hdf5.py | 9 ++- dsms/knowledge/semantics/units/sparql.py | 66 ++++--------------- examples/unit.py | 11 ++++ 4 files changed, 34 insertions(+), 54 deletions(-) create mode 100644 examples/unit.py diff --git a/dsms/knowledge/properties/custom_datatype/numerical.py b/dsms/knowledge/properties/custom_datatype/numerical.py index f4e4754..8abac1b 100644 --- a/dsms/knowledge/properties/custom_datatype/numerical.py +++ b/dsms/knowledge/properties/custom_datatype/numerical.py @@ -48,7 +48,7 @@ def convert_to( self, unit_symbol_or_iri: str, decimals: "Optional[int]" = None, - use_input_iri: bool = False, + use_input_iri: bool = True, ) -> float: """ Convert the data of property to a different unit. diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index 0242556..2695de2 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -54,7 +54,7 @@ def convert_to( self, unit_symbol_or_iri: str, decimals: "Optional[int]" = None, - use_input_iri: bool = False, + use_input_iri: bool = True, ) -> "List[Any]": """ Convert the data of the column to a different unit. @@ -118,3 +118,10 @@ def get(self, name: str) -> "Optional[Column]": if column.name == name: response = column return response + + def __getattr__(self, key): + """Return column as attribute.""" + attribute = self.get(key) + if not attribute: + raise AttributeError(f"{self} has no attribute '{key}'") + return attribute diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py index 16be8df..5fe4b4f 100644 --- a/dsms/knowledge/semantics/units/sparql.py +++ b/dsms/knowledge/semantics/units/sparql.py @@ -22,57 +22,19 @@ def query(cls) -> str: raise ValueError("Property name must be defined.") url = urljoin(str(cls.dsms.config.host_url), str(kitem_id)) - if cls.kwargs.get("is_hdf5_column"): - query = f""" - prefix datamodel: - prefix metro: - prefix math: - prefix perceptual: - prefix reductionistic: - - select distinct - (STR(?symbol_literal) as ?symbol) - ?iri - where {{ - - <{url}/dataset> datamodel:composition ?prop . - - ?prop rdfs:label "{property_name}" ; - rdf:type datamodel:DataInstance ; - metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?literal, ?unit . - ?unit rdf:type ?iri . - ?literal a metro:UnitLiteral ; - metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?symbol_literal . - FILTER(?iri != metro:UnitLiteral) - }} - """ - - else: - query = f""" - prefix datamodel: - prefix metro: - prefix math: - prefix perceptual: - prefix reductionistic: - - select distinct - ?symbol - ?iri + return f"""prefix csvw: + prefix dcat: + prefix qudt: + prefix xsd: + prefix fileid: <{url}/> + select distinct ?iri where {{ - - <{url}/dataset> datamodel:composition ?prop . - - ?prop rdfs:label "{property_name}" ; - rdf:type datamodel:Metadata ; - metro:EMMO_8ef3cd6d_ae58_4a8d_9fc0_ad8f49015cd0 ?numeric . - - ?numeric rdf:type math:EMMO_4ce76d7f_03f8_45b6_9003_90052a79bfaa ; - metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?unit , ?literal . - - ?unit rdf:type ?iri . - ?literal a metro:UnitLiteral ; - metro:EMMO_67fc0a36_8dcb_4ffa_9a43_31074efa3296 ?symbol_literal . - FILTER(?iri != metro:UnitLiteral) + bind( + <{url}> as ?g + ) + {{ + graph ?g {{ + fileid:{property_name} qudt:hasUnit ?iri . + }} }} - """ - return query + }}""" diff --git a/examples/unit.py b/examples/unit.py new file mode 100644 index 0000000..5246599 --- /dev/null +++ b/examples/unit.py @@ -0,0 +1,11 @@ +from dsms import DSMS + +dsms = DSMS(env="../.env") + +item = dsms.search(query="DX56_D_FZ2_WR00_43", allow_fuzzy=False, limit=1)[0] + +print(item.custom_properties.SpecimenWidth.convert_to("m")) + +print(item.hdf5.Extension.convert_to("m")[:100]) + +print(item) From 9472c1e375a3f4539795935235884bbfee1e3c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 17 Apr 2024 02:13:23 +0200 Subject: [PATCH 027/114] Bump version v1.4.0rc1 -> v1.4.0rc2 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 5fff26e..b1777d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc1 +version = v1.4.0rc2 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 87b9792841834bee75a3f301662eb6663c44a47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 17 Apr 2024 02:49:18 +0200 Subject: [PATCH 028/114] update printing of kitems --- dsms/knowledge/properties/apps.py | 11 +++++++++++ dsms/knowledge/properties/base.py | 6 +++--- dsms/knowledge/utils.py | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 386fba0..0db76a6 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -22,6 +22,17 @@ class AdditionalProperties(BaseModel): description="File extensions for which the upload shall be triggered.", ) + def __str__(self) -> str: + """Pretty print the KProperty""" + values = ", ".join( + [f"{key}: {value}" for key, value in self.__dict__.items()] + ) + return f"{{{values}}}" + + def __repr__(self) -> str: + """Pretty print the Apps""" + return str(self) + class App(KPropertyItem): """App of a KItem.""" diff --git a/dsms/knowledge/properties/base.py b/dsms/knowledge/properties/base.py index 91b7191..892c022 100644 --- a/dsms/knowledge/properties/base.py +++ b/dsms/knowledge/properties/base.py @@ -39,14 +39,14 @@ class KPropertyItem(BaseModel): def __str__(self) -> str: """Pretty print the KProperty""" - values = ", ".join( + values = ",\n\t\t\t".join( [ - f"{key}={value}" + f"{key}: {value}" for key, value in self.__dict__.items() if key not in self.exclude ] ) - return f"{self.__class__.__name__}({values})" + return f"{{\n\t\t\t{values}\n\t\t}}" def __repr__(self) -> str: """Pretty print the KProperty""" diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index e7cddb9..a2b1d6c 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -98,12 +98,12 @@ def _create_custom_properties_model( def _print_properties(self: Any) -> str: fields = ", \n".join( [ - f"\t\t{key}={value}" + f"\t\t{key}: {value}" for key, value in self.model_dump().items() if key not in self.model_config["exclude"] ] ) - return f"{{\n{fields}\n\t\t}}" + return f"{{\n{fields}\n\t}}" def __setattr_property__(self, key, value) -> None: From 108872b000f09181247f6d8203e103de1692adcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 18 Apr 2024 01:09:27 +0200 Subject: [PATCH 029/114] adapt to changes in new knowledge service --- dsms/core/dsms.py | 3 ++- dsms/knowledge/search.py | 17 +++++++++++++++++ dsms/knowledge/utils.py | 32 +++++++++++++++++++------------- examples/unit.py | 8 ++++---- 4 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 dsms/knowledge/search.py diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index 5c673b2..505d02d 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -27,6 +27,7 @@ from dsms.core.context import Buffers from dsms.knowledge.kitem import KItem from dsms.knowledge.ktype import KType + from dsms.knowledge.search import SearchResult class DSMS: @@ -128,7 +129,7 @@ def search( annotations: "Optional[List[str]]" = [], limit: int = 10, allow_fuzzy: "Optional[bool]" = True, - ) -> "List[KItem]": + ) -> "List[SearchResult]": """Search for KItems in the remote backend.""" return _search(query, ktypes, annotations, limit, allow_fuzzy) diff --git a/dsms/knowledge/search.py b/dsms/knowledge/search.py new file mode 100644 index 0000000..5b7f8b6 --- /dev/null +++ b/dsms/knowledge/search.py @@ -0,0 +1,17 @@ +"""DSMS search model""" + +from typing import TYPE_CHECKING + +from pydantic import BaseModel, Field + +if TYPE_CHECKING: + from dsms import KItem + + +class SearchResult(BaseModel): + """DSMS search result""" + + hit: "KItem" = Field(..., description="KItem returned by the search") + fuzzy: bool = Field( + ..., description="Whether the KItem was found through a similarity hit" + ) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index a2b1d6c..19a7223 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -25,6 +25,8 @@ NumericalDataType, ) +from dsms.knowledge.search import SearchResult # isort:skip + if TYPE_CHECKING: from dsms.core.context import Buffers from dsms.knowledge import KItem, KType @@ -46,20 +48,19 @@ def _create_custom_properties_model( fields = {} if isinstance(value, dict): - title = _name_to_camel(value.get("title")) - for item in value.get("objects"): + for item in value.get("sections"): for form_input in item.get("inputs"): label = form_input.get("label") dtype = form_input.get("widget") default = form_input.get("defaultValue") slug = _slugify(label) - if dtype in ("text", "file"): + if dtype in ("Text", "File", "Textarea", "Vocabulary term"): dtype = str - elif dtype in ("number", "slider"): + elif dtype in ("Number", "Slider"): dtype = NumericalDataType - elif dtype == "checkbox": + elif dtype == "Checkbox": dtype = bool - elif dtype in ("select", "radio"): + elif dtype in ("Select", "Radio"): dtype = Enum( _name_to_camel(label) + "Choices", { @@ -67,14 +68,13 @@ def _create_custom_properties_model( for choice in form_input.get("choices") }, ) - elif dtype == "knowledge-select": + elif dtype == "Knowledge item": warnings.warn( - "knowledge-select not fully supported for KTypes yet." + "knowledge item not fully supported for KTypes yet." ) dtype = str + fields[slug] = (dtype, default or None) - else: - title = "CustomPropertiesModel" fields["kitem"] = ( Optional[KItem], Field(None, exclude=True), @@ -87,7 +87,10 @@ def _create_custom_properties_model( "validate_model": model_validator(mode="before")(_validate_model) } model = create_model( - title, __config__=config, __validators__=validators, **fields + "CustomPropertiesModel", + __config__=config, + __validators__=validators, + **fields, ) setattr(model, "__str__", _print_properties) setattr(model, "__repr__", _print_properties) @@ -424,7 +427,7 @@ def _search( annotations: "Optional[List[str]]" = [], limit: "Optional[int]" = 10, allow_fuzzy: "Optional[bool]" = True, -) -> "List[KItem]": +) -> "List[SearchResult]": """Search for KItems in the remote backend""" from dsms import KItem # isort:skip @@ -450,7 +453,10 @@ def _search( raise RuntimeError( f"""Something went wrong while searching for KItems: {response.text}""" ) from excep - return [KItem(**item) for item in dumped] + return [ + SearchResult(hit=KItem(**item.get("hit")), fuzzy=item.get("fuzzy")) + for item in dumped + ] def _slugify(input_string: str, replacement: str = ""): diff --git a/examples/unit.py b/examples/unit.py index 5246599..94f1c3b 100644 --- a/examples/unit.py +++ b/examples/unit.py @@ -2,10 +2,10 @@ dsms = DSMS(env="../.env") -item = dsms.search(query="DX56_D_FZ2_WR00_43", allow_fuzzy=False, limit=1)[0] +item = dsms.search(query="AFZ1-Fz-S1Q", allow_fuzzy=False, limit=1)[0].hit -print(item.custom_properties.SpecimenWidth.convert_to("m")) +print(item) -print(item.hdf5.Extension.convert_to("m")[:100]) +print(item.custom_properties.OriginalWidth.convert_to("m")) -print(item) +print(item.hdf5.PercentageExtension.get()[:100]) From 691db50a48a2d511915f816aa9918cfda0385a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 18 Apr 2024 01:37:59 +0200 Subject: [PATCH 030/114] adapt payload of custom properties and basic usage notebook --- dsms/knowledge/utils.py | 2 +- examples/basic_usage.ipynb | 1487 +++++++++++++++++------------------- 2 files changed, 683 insertions(+), 806 deletions(-) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 19a7223..79d7dfd 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -264,7 +264,7 @@ def _update_kitem(kitem: "KItem") -> Response: }, exclude_none=True, ) - custom_properties = kitem.custom_properties.model_dump_json() + custom_properties = kitem.custom_properties.model_dump() payload = json.loads(dumped) payload.update( custom_properties={"content": custom_properties}, **differences diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 5641041..e44adc4 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -59,109 +59,6 @@ "We can see which kind of DSMS-object we own as a user:" ] }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = YS2-Fz-D3Q, \n", - " \n", - " \tid = 367a77d1-1ebd-425c-b9a1-bd422e55fc92, \n", - " \n", - " \tktype_id = dataset, \n", - " \n", - " \tslug = ys2fzd3q, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-25 09:58:21.246762, \n", - " \n", - " \tupdated_at = 2024-03-25 09:58:21.246762, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = YS2-Fz-D3L, \n", - " \n", - " \tid = 05ece5dc-8062-4f8e-8d01-939670b1ea11, \n", - " \n", - " \tktype_id = dataset, \n", - " \n", - " \tslug = ys2fzd3l, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-25 09:57:24.632875, \n", - " \n", - " \tupdated_at = 2024-03-25 09:57:24.632875, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.kitems" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -191,12 +88,7 @@ "KTypes.Expert\n", "KTypes.App\n", "KTypes.DatasetCatalog\n", - "KTypes.TestSeries\n", - "KTypes.TestMatthias\n", - "KTypes.Characterizationprocedure\n", - "KTypes.Dataset\n", - "KTypes.Specimen\n", - "KTypes.Formtest2\n" + "KTypes.Dataset\n" ] } ], @@ -231,11 +123,11 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 680904c8-d502-41bd-9b0c-96da6891dcce, \n", + "\tid = 6630c022-5c65-4fe4-a109-58828aedf145, \n", "\n", - "\tktype_id = KTypes.DatasetCatalog, \n", + "\tktype_id = KTypes.Dataset, \n", "\n", - "\tslug = foo123, \n", + "\tslug = foo123-6630c022, \n", "\n", "\tannotations = [], \n", "\n", @@ -264,8 +156,8 @@ "\tuser_groups = [], \n", "\n", "\tcustom_properties = {\n", - "\t\tfoo=bar\n", - "\t\t}, \n", + "\t\tfoo: bar\n", + "\t}, \n", "\n", "\thdf5 = None\n", ")" @@ -279,7 +171,7 @@ "source": [ "item = KItem(\n", " name=\"foo123\",\n", - " ktype_id=dsms.ktypes.DatasetCatalog,\n", + " ktype_id=dsms.ktypes.Dataset,\n", " custom_properties={\"foo\": \"bar\"},\n", ")\n", "\n", @@ -297,9 +189,21 @@ "cell_type": "code", "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'https://stahldigital.materials-data.space/dataset/foo123-6630c022'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "dsms.commit()" + "dsms.commit()\n", + "item.url" ] }, { @@ -321,11 +225,11 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 680904c8-d502-41bd-9b0c-96da6891dcce, \n", + "\tid = 6630c022-5c65-4fe4-a109-58828aedf145, \n", "\n", - "\tktype_id = dataset-catalog, \n", + "\tktype_id = dataset, \n", "\n", - "\tslug = foo123, \n", + "\tslug = foo123-6630c022, \n", "\n", "\tannotations = [], \n", "\n", @@ -336,16 +240,18 @@ "\taffiliations = [], \n", "\n", "\tauthors = [\n", - "\t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + "\t\t{\n", + "\t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + "\t\t}\n", "\t], \n", "\n", "\tavatar_exists = False, \n", "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-04-11 12:28:26.391526, \n", + "\tcreated_at = 2024-04-17 23:30:44.313881, \n", "\n", - "\tupdated_at = 2024-04-11 12:28:26.391526, \n", + "\tupdated_at = 2024-04-17 23:30:44.313881, \n", "\n", "\texternal_links = [], \n", "\n", @@ -356,8 +262,8 @@ "\tuser_groups = [], \n", "\n", "\tcustom_properties = {\n", - "\t\tfoo=bar\n", - "\t\t}, \n", + "\t\tfoo: bar\n", + "\t}, \n", "\n", "\thdf5 = None\n", ")" @@ -372,160 +278,6 @@ "item" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And the list of our KItems in the DSMS has been updated as well:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = YS2-Fz-D3Q, \n", - " \n", - " \tid = 367a77d1-1ebd-425c-b9a1-bd422e55fc92, \n", - " \n", - " \tktype_id = dataset, \n", - " \n", - " \tslug = ys2fzd3q, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-25 09:58:21.246762, \n", - " \n", - " \tupdated_at = 2024-03-25 09:58:21.246762, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = foo123, \n", - " \n", - " \tid = 680904c8-d502-41bd-9b0c-96da6891dcce, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = foo123, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-11 12:28:26.391526, \n", - " \n", - " \tupdated_at = 2024-04-11 12:28:26.391526, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = {\n", - " \t\tfoo=bar\n", - " \t\t}, \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = YS2-Fz-D3L, \n", - " \n", - " \tid = 05ece5dc-8062-4f8e-8d01-939670b1ea11, \n", - " \n", - " \tktype_id = dataset, \n", - " \n", - " \tslug = ys2fzd3l, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-25 09:57:24.632875, \n", - " \n", - " \tupdated_at = 2024-03-25 09:57:24.632875, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.kitems" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -547,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -581,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -604,20 +356,57 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { - "ename": "RuntimeError", - "evalue": "Download for attachment `..\\README.md` was not successful: {\"detail\":\"File not found\"}", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[11], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m file \u001b[38;5;129;01min\u001b[39;00m item\u001b[38;5;241m.\u001b[39mattachments:\n\u001b[1;32m----> 2\u001b[0m download \u001b[38;5;241m=\u001b[39m \u001b[43mfile\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdownload\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;124m Downloaded file:\u001b[39m\u001b[38;5;124m\"\u001b[39m, file)\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m|------------------------------------Beginning of file------------------------------------|\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "File \u001b[1;32mc:\\users\\bue\\dsms-python-sdk\\dsms\\knowledge\\properties\\attachments.py:21\u001b[0m, in \u001b[0;36mAttachment.download\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdownload\u001b[39m(\u001b[38;5;28mself\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mstr\u001b[39m:\n\u001b[0;32m 20\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Download attachment file\"\"\"\u001b[39;00m\n\u001b[1;32m---> 21\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_get_attachment\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mid\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mname\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mc:\\users\\bue\\dsms-python-sdk\\dsms\\knowledge\\utils.py:344\u001b[0m, in \u001b[0;36m_get_attachment\u001b[1;34m(kitem_id, file_name)\u001b[0m\n\u001b[0;32m 342\u001b[0m response \u001b[38;5;241m=\u001b[39m _perform_request(url, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mget\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 343\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m response\u001b[38;5;241m.\u001b[39mok:\n\u001b[1;32m--> 344\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\n\u001b[0;32m 345\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDownload for attachment `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfile_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m` was not successful: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse\u001b[38;5;241m.\u001b[39mtext\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 346\u001b[0m )\n\u001b[0;32m 347\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m response\u001b[38;5;241m.\u001b[39mtext\n", - "\u001b[1;31mRuntimeError\u001b[0m: Download for attachment `..\\README.md` was not successful: {\"detail\":\"File not found\"}" + "name": "stdout", + "output_type": "stream", + "text": [ + "\t\t\t Downloaded file: {\n", + "\t\t\tname: README.md\n", + "\t\t}\n", + "|------------------------------------Beginning of file------------------------------------|\n", + "# DSMS-SDK\n", + "Python SDK core-package for interacting with the Dataspace Management System (DSMS)\n", + "\n", + "\n", + "## Authors\n", + "\n", + "[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", + "\n", + "[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", + "\n", + "[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", + "\n", + "## License\n", + "\n", + "This project is licensed under the BSD 3-Clause. See the LICENSE file for more information.\n", + "\n", + "## Usage\n", + "\n", + "The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities:\n", + "\n", + "- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType)\n", + " - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test\n", + " - Administrating authorship, contact information and supplementary information upon making changes or adding KItems\n", + " - Semantic annotation of KItems\n", + "- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface\n", + "- Linking KItems to other KItems\n", + "- Linking Apps to KItems, triggererd, for example, during a file upload\n", + "- Performing simple file upload and download using attachments to KItems\n", + "- Export of a knowledge (sub) graph as common serializations (.ttl, .json)\n", + "\n", + "For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items.\n", + "\n", + "\n", + "## Disclaimer\n", + "\n", + "Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM.\n", + "\n", + "Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de)\n", + "\n", + "|---------------------------------------End of file---------------------------------------|\n" ] } ], @@ -653,16 +442,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "UserGroup(name=foogroup, group_id=123)" + "{\n", + "\t\t\tname: foogroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}" ] }, - "execution_count": 13, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -684,7 +476,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -700,7 +492,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -710,11 +502,11 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", + "\tid = 6630c022-5c65-4fe4-a109-58828aedf145, \n", "\n", - "\tktype_id = dataset-catalog, \n", + "\tktype_id = dataset, \n", "\n", - "\tslug = foo123, \n", + "\tslug = foo123-6630c022, \n", "\n", "\tannotations = [], \n", "\n", @@ -725,16 +517,18 @@ "\taffiliations = [], \n", "\n", "\tauthors = [\n", - "\t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + "\t\t{\n", + "\t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + "\t\t}\n", "\t], \n", "\n", "\tavatar_exists = False, \n", "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-03-06 10:12:39.377020, \n", + "\tcreated_at = 2024-04-17 23:30:44.313881, \n", "\n", - "\tupdated_at = 2024-03-06 10:13:06.308318, \n", + "\tupdated_at = 2024-04-17 23:30:57.022527, \n", "\n", "\texternal_links = [], \n", "\n", @@ -744,13 +538,15 @@ "\n", "\tuser_groups = [], \n", "\n", - "\tcustom_properties = CustomProperties(content={'foobar': 'foobar'}), \n", + "\tcustom_properties = {\n", + "\t\tfoo: bar\n", + "\t}, \n", "\n", "\thdf5 = None\n", ")" ] }, - "execution_count": 15, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -768,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -784,7 +580,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -800,40 +596,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "dsms.commit()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see know, our KItem has been removed from our inventory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.kitems" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -856,7 +625,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -905,129 +674,93 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[KItem(\n", - " \n", - " \tname = excel_component_test, \n", - " \n", - " \tid = 87eed190-3979-4c5f-bae5-2dbad25f2f12, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_component_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", + "[SearchResult(hit=KItem(\n", " \n", - " \tcreated_at = 2024-03-05 16:45:15.506823, \n", + " \tname = csv_test_bulge, \n", " \n", - " \tupdated_at = 2024-03-05 16:45:15.506823, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_ebsd, \n", - " \n", - " \tid = c113be0f-77cf-447e-b14f-a84c893cec23, \n", + " \tid = 2539dfde-abc9-4fdd-9204-026252ee952c, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_ebsd, \n", + " \tslug = csvtestbulge, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang), \n", - " \t\tAttachment(name=DP800_111_WR00_500x_150x150um.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR00_196_merged_cleaned.ang), \n", - " \t\tAttachment(name=DP800_112_WR90_500x_150x150.ang), \n", - " \t\tAttachment(name=dx56_mitte_quer_850x850_step1-5.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR90_197_merged_cleaned.ang), \n", - " \t\tAttachment(name=dx56_mitte_laengs_850x850_step1-5.ang)\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43 1.txt\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", + " \t\t\tsource_id: 2539dfde-abc9-4fdd-9204-026252ee952c\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:45:48.101341, \n", + " \tcreated_at = 2024-02-15 13:05:49.418303, \n", " \n", - " \tupdated_at = 2024-03-05 16:45:48.101341, \n", + " \tupdated_at = 2024-02-15 13:05:49.418303, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [], \n", + " \tkitem_apps = [\n", + " \t\t{\n", + " \t\t\tkitem_app_id: 6,\n", + " \t\t\texecutable: csv_tensile_test.ipynb,\n", + " \t\t\ttitle: csv_notebook,\n", + " \t\t\tdescription: Csv_notebook,\n", + " \t\t\ttags: None,\n", + " \t\t\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv', '.txt']}\n", + " \t\t}\n", + " \t], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_bulgetest, \n", + " \tname = EBSD_HX340LAD, \n", " \n", - " \tid = e64c512a-c2e5-428d-bc85-5d0d9c2d68c7, \n", + " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_bulgetest, \n", + " \tslug = ebsdhx340lad, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=DX56_C_BLG_WR90_42.TXT), \n", - " \t\tAttachment(name=AA6014_O_BLG_WR90_241.TXT), \n", - " \t\tAttachment(name=DP800_H_BLG_WR90_140.TXT), \n", - " \t\tAttachment(name=DX56_D_BLG_WR90_56.TXT), \n", - " \t\tAttachment(name=AFZ1-BU-S8.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S1.TXT), \n", - " \t\tAttachment(name=AA6014_N_BLG_WR90_218.TXT), \n", - " \t\tAttachment(name=DP800_G_BLG_WR90_126.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S7.TXT), \n", - " \t\tAttachment(name=AA6014_M_BLG_WR90_195.TXT), \n", - " \t\tAttachment(name=DX56_B_BLG_WR90_28.TXT), \n", - " \t\tAttachment(name=DP800_F_BLG_WR90_108.TXT)\n", + " \t\t{\n", + " \t\t\tname: 11533_FS_Scan1_830x830_100x.ang\n", + " \t\t}\n", " \t], \n", " \n", " \tlinked_kitems = [], \n", @@ -1035,16 +768,18 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:46:41.767008, \n", + " \tcreated_at = 2024-02-20 14:41:23.907652, \n", " \n", - " \tupdated_at = 2024-03-05 16:46:41.767008, \n", + " \tupdated_at = 2024-02-20 14:41:23.907652, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1054,39 +789,52 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = excel_shear_tensile_test, \n", + " \tname = testtest, \n", " \n", - " \tid = e9c4bb04-9482-458d-9d12-c2ad8f214930, \n", + " \tid = 0f995978-9932-436a-9d76-961463457ed2, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = excel_shear_tensile_test, \n", + " \tslug = testtest, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: http://www.oie.eu/ontology/material#SinglePhaseAlloy,\n", + " \t\t\tname: SinglePhaseAlloy,\n", + " \t\t\tnamespace: material\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", + " \t\t\tsource_id: 0f995978-9932-436a-9d76-961463457ed2\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: de5e5310-ae4b-4383-b1d5-1fc6cbbc5f2f\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:47:27.228060, \n", + " \tcreated_at = 2024-03-14 08:44:46.504818, \n", " \n", - " \tupdated_at = 2024-03-05 16:47:27.228060, \n", + " \tupdated_at = 2024-03-14 08:44:46.504818, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1096,19 +844,19 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = excel_tensile_test, \n", + " \tname = my_tensiletest, \n", " \n", - " \tid = e1f1b94c-5bd6-44c7-9de4-1d01650ac7b8, \n", + " \tid = e0b09fe1-5425-4053-abc3-86e7e9e49b25, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = excel_tensile_test, \n", + " \tslug = mytensiletest, \n", " \n", " \tannotations = [], \n", " \n", @@ -1119,99 +867,97 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:48:01.936787, \n", + " \tcreated_at = 2024-03-15 08:41:32.727310, \n", " \n", - " \tupdated_at = 2024-03-05 16:48:01.936787, \n", + " \tupdated_at = 2024-03-15 08:41:32.727310, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=105, executable=excel_tensile_test/excel_tensile_test.ipynb, title=excel_tensile_test, description=excel_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.xls', '.xlsm'])\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_tensile_test, \n", + " \tname = ggggg, \n", " \n", - " \tid = 90207798-d6b2-47fb-b208-f7b8f771de0d, \n", + " \tid = 970f7396-a71a-49bd-b16e-58e412fe6c54, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_tensile_test, \n", - " \n", - " \tannotations = [], \n", + " \tslug = ggggg, \n", " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DX56_D_FZ2_WR00_43.TXT)\n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: http://www.oie.eu/ontology/material#Metallic,\n", + " \t\t\tname: Metallic,\n", + " \t\t\tnamespace: material\n", + " \t\t}\n", " \t], \n", " \n", + " \tattachments = [], \n", + " \n", " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 17:08:20.345033, \n", + " \tcreated_at = 2024-03-19 14:20:27.540398, \n", " \n", - " \tupdated_at = 2024-03-05 17:08:20.345033, \n", + " \tupdated_at = 2024-03-19 14:20:27.540398, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=107, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT']), \n", - " \t\tApp(kitem_app_id=108, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", - " \thdf5 = [\n", - " \t\tColumn(column_id=0, name=Prüfzeit), \n", - " \t\tColumn(column_id=1, name=Standardkraft), \n", - " \t\tColumn(column_id=2, name=Traversenweg absolut), \n", - " \t\tColumn(column_id=3, name=Standardweg), \n", - " \t\tColumn(column_id=4, name=Breitenänderung), \n", - " \t\tColumn(column_id=5, name=Dehnung)\n", - " \t]\n", - " ),\n", - " KItem(\n", + " \thdf5 = None\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_tensile_test_f2, \n", + " \tname = csv_test, \n", " \n", - " \tid = c9203684-f1ce-4d23-92f9-478a002b7440, \n", + " \tid = 99bbb5d3-add5-42b0-9b75-93ee29b01766, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_tensile_test_f2, \n", + " \tslug = csvtest, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=DP800_F_FZ2_WR15_97.TXT)\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", " \t], \n", " \n", " \tlinked_kitems = [], \n", @@ -1219,65 +965,67 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 17:08:27.579333, \n", + " \tcreated_at = 2024-04-16 18:32:06.073842, \n", " \n", - " \tupdated_at = 2024-03-05 17:08:27.579333, \n", + " \tupdated_at = 2024-04-16 18:32:06.073842, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=109, executable=csv_tensile_test_f2/csv_tensile_test_f2.ipynb, title=csv_tensile_test_f2, description=csv_tensile_test_f2, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = material constants, \n", + " \tname = foo 1, \n", " \n", - " \tid = ea0bb87b-3a26-48be-84e1-bec9b99a1204, \n", + " \tid = 8e3861e6-3479-411d-a460-addb91459da4, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = material_constants, \n", + " \tslug = foo1-8e3861e6, \n", " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/MaterialCard, name=MaterialCard, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204)\n", + " \t\t{\n", + " \t\t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89,\n", + " \t\t\tsource_id: 8e3861e6-3479-411d-a460-addb91459da4\n", + " \t\t}\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-27 05:31:47.433035, \n", + " \tcreated_at = 2024-04-17 23:31:25.540343, \n", " \n", - " \tupdated_at = 2024-02-27 05:31:47.433035, \n", + " \tupdated_at = 2024-04-17 23:31:25.540343, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1287,46 +1035,48 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={'material constants': {'C11': 226000.0, 'C12': 140000.0, 'C44': 116000.0, 'shear_rate_ref': 0.001, 'rate_sens': 0.0125, 'tau0': 62.1930648, 'tau1': 48.1881715, 'theta0': 356.842243, 'theta1': 37.4360252, 'q1': 1.4, 'q2': 1.4}}), \n", + " \tcustom_properties = {\n", + " \t\tid: 8e3861e6-3479-411d-a460-addb91459da4, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = orientations, \n", + " \tname = test, \n", " \n", - " \tid = 5386bf18-533a-4976-8481-d54638f07a91, \n", + " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = orientations, \n", + " \tslug = test, \n", " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/GrainOrientation, name=GrainOrientation, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang)\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=5386bf18-533a-4976-8481-d54638f07a91), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=5386bf18-533a-4976-8481-d54638f07a91)\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-27 05:31:47.498209, \n", + " \tcreated_at = 2024-04-16 18:33:54.428378, \n", " \n", - " \tupdated_at = 2024-02-27 05:31:47.498209, \n", + " \tupdated_at = 2024-04-16 18:33:54.428378, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1336,45 +1086,41 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = rve_1000_grains_geofile, \n", + " \tname = aaaaaa, \n", " \n", - " \tid = 13e57827-c5fe-413e-90dd-bb95ec5c1d3a, \n", + " \tid = 6afc62f5-0da2-418f-9372-ae030a3e5e34, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = rve_1000_grains_geofile, \n", + " \tslug = aaaaaa, \n", " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/Geometry, name=Geometry, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", + " \tannotations = [], \n", " \n", - " \tattachments = [\n", - " \t\tAttachment(name=mesh_40x40x40_grains_1000_aspect_ratio_1_6.geo)\n", - " \t], \n", + " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=13e57827-c5fe-413e-90dd-bb95ec5c1d3a)\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-27 05:31:47.588776, \n", + " \tcreated_at = 2024-03-26 08:12:27.156269, \n", " \n", - " \tupdated_at = 2024-02-27 05:31:47.588776, \n", + " \tupdated_at = 2024-03-26 08:12:27.156269, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1384,41 +1130,41 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = new1, \n", " \n", - " \tid = 8377f120-d558-4cc2-93d4-582bb01fac11, \n", + " \tid = 47950cae-fd39-430c-8bb5-3f894a032d77, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1, \n", + " \tslug = new1, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=5dacf60c-6a62-466a-a85e-9f3ce422cefa, source_id=8377f120-d558-4cc2-93d4-582bb01fac11)\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-06 10:13:29.213578, \n", + " \tcreated_at = 2024-04-16 18:36:49.861912, \n", " \n", - " \tupdated_at = 2024-03-06 10:13:29.213578, \n", + " \tupdated_at = 2024-04-16 18:36:49.861912, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1428,13 +1174,13 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " )]" + " ), fuzzy=False)]" ] }, - "execution_count": 21, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1452,41 +1198,62 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[KItem(\n", + "[SearchResult(hit=KItem(\n", " \n", - " \tname = excel_component_test, \n", + " \tname = Fraunhofer, \n", " \n", - " \tid = 87eed190-3979-4c5f-bae5-2dbad25f2f12, \n", + " \tid = 6c695c8b-06ec-4df8-b840-d2dc0752e22d, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", - " \tslug = excel_component_test, \n", + " \tslug = fraunhofer, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: http://www.oie.eu/ontology/characterization-method#ElectricalCharacterisationMethods,\n", + " \t\t\tname: ElectricalCharacterisationMethods,\n", + " \t\t\tnamespace: method\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 2539dfde-abc9-4fdd-9204-026252ee952c,\n", + " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b23df795-9441-4a0d-993a-aaeee53c4164,\n", + " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 0f995978-9932-436a-9d76-961463457ed2,\n", + " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 8d01f084-3604-4956-bd42-a09610e71d0e\n", + " \t\t}\n", " \t], \n", " \n", - " \tavatar_exists = False, \n", + " \tavatar_exists = True, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:45:15.506823, \n", + " \tcreated_at = 2024-02-13 15:20:23.000643, \n", " \n", - " \tupdated_at = 2024-03-05 16:45:15.506823, \n", + " \tupdated_at = 2024-02-13 15:20:23.000643, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1496,85 +1263,91 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_ebsd, \n", + " \tname = csv_test_bulge, \n", " \n", - " \tid = c113be0f-77cf-447e-b14f-a84c893cec23, \n", + " \tid = 2539dfde-abc9-4fdd-9204-026252ee952c, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_ebsd, \n", + " \tslug = csvtestbulge, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang), \n", - " \t\tAttachment(name=DP800_111_WR00_500x_150x150um.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR00_196_merged_cleaned.ang), \n", - " \t\tAttachment(name=DP800_112_WR90_500x_150x150.ang), \n", - " \t\tAttachment(name=dx56_mitte_quer_850x850_step1-5.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR90_197_merged_cleaned.ang), \n", - " \t\tAttachment(name=dx56_mitte_laengs_850x850_step1-5.ang)\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43 1.txt\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", + " \t\t\tsource_id: 2539dfde-abc9-4fdd-9204-026252ee952c\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:45:48.101341, \n", + " \tcreated_at = 2024-02-15 13:05:49.418303, \n", " \n", - " \tupdated_at = 2024-03-05 16:45:48.101341, \n", + " \tupdated_at = 2024-02-15 13:05:49.418303, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [], \n", + " \tkitem_apps = [\n", + " \t\t{\n", + " \t\t\tkitem_app_id: 6,\n", + " \t\t\texecutable: csv_tensile_test.ipynb,\n", + " \t\t\ttitle: csv_notebook,\n", + " \t\t\tdescription: Csv_notebook,\n", + " \t\t\ttags: None,\n", + " \t\t\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv', '.txt']}\n", + " \t\t}\n", + " \t], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_bulgetest, \n", + " \tname = EBSD_HX340LAD, \n", " \n", - " \tid = e64c512a-c2e5-428d-bc85-5d0d9c2d68c7, \n", + " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_bulgetest, \n", + " \tslug = ebsdhx340lad, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=DX56_C_BLG_WR90_42.TXT), \n", - " \t\tAttachment(name=AA6014_O_BLG_WR90_241.TXT), \n", - " \t\tAttachment(name=DP800_H_BLG_WR90_140.TXT), \n", - " \t\tAttachment(name=DX56_D_BLG_WR90_56.TXT), \n", - " \t\tAttachment(name=AFZ1-BU-S8.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S1.TXT), \n", - " \t\tAttachment(name=AA6014_N_BLG_WR90_218.TXT), \n", - " \t\tAttachment(name=DP800_G_BLG_WR90_126.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S7.TXT), \n", - " \t\tAttachment(name=AA6014_M_BLG_WR90_195.TXT), \n", - " \t\tAttachment(name=DX56_B_BLG_WR90_28.TXT), \n", - " \t\tAttachment(name=DP800_F_BLG_WR90_108.TXT)\n", + " \t\t{\n", + " \t\t\tname: 11533_FS_Scan1_830x830_100x.ang\n", + " \t\t}\n", " \t], \n", " \n", " \tlinked_kitems = [], \n", @@ -1582,16 +1355,18 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:46:41.767008, \n", + " \tcreated_at = 2024-02-20 14:41:23.907652, \n", " \n", - " \tupdated_at = 2024-03-05 16:46:41.767008, \n", + " \tupdated_at = 2024-02-20 14:41:23.907652, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1601,39 +1376,52 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = excel_shear_tensile_test, \n", + " \tname = testtest, \n", " \n", - " \tid = e9c4bb04-9482-458d-9d12-c2ad8f214930, \n", + " \tid = 0f995978-9932-436a-9d76-961463457ed2, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = excel_shear_tensile_test, \n", + " \tslug = testtest, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: http://www.oie.eu/ontology/material#SinglePhaseAlloy,\n", + " \t\t\tname: SinglePhaseAlloy,\n", + " \t\t\tnamespace: material\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", + " \t\t\tsource_id: 0f995978-9932-436a-9d76-961463457ed2\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: de5e5310-ae4b-4383-b1d5-1fc6cbbc5f2f\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:47:27.228060, \n", + " \tcreated_at = 2024-03-14 08:44:46.504818, \n", " \n", - " \tupdated_at = 2024-03-05 16:47:27.228060, \n", + " \tupdated_at = 2024-03-14 08:44:46.504818, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1643,19 +1431,19 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = excel_tensile_test, \n", + " \tname = my_tensiletest, \n", " \n", - " \tid = e1f1b94c-5bd6-44c7-9de4-1d01650ac7b8, \n", + " \tid = e0b09fe1-5425-4053-abc3-86e7e9e49b25, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = excel_tensile_test, \n", + " \tslug = mytensiletest, \n", " \n", " \tannotations = [], \n", " \n", @@ -1666,99 +1454,97 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 16:48:01.936787, \n", + " \tcreated_at = 2024-03-15 08:41:32.727310, \n", " \n", - " \tupdated_at = 2024-03-05 16:48:01.936787, \n", + " \tupdated_at = 2024-03-15 08:41:32.727310, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=105, executable=excel_tensile_test/excel_tensile_test.ipynb, title=excel_tensile_test, description=excel_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.xls', '.xlsm'])\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_tensile_test, \n", + " \tname = ggggg, \n", " \n", - " \tid = 90207798-d6b2-47fb-b208-f7b8f771de0d, \n", + " \tid = 970f7396-a71a-49bd-b16e-58e412fe6c54, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_tensile_test, \n", - " \n", - " \tannotations = [], \n", + " \tslug = ggggg, \n", " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DX56_D_FZ2_WR00_43.TXT)\n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: http://www.oie.eu/ontology/material#Metallic,\n", + " \t\t\tname: Metallic,\n", + " \t\t\tnamespace: material\n", + " \t\t}\n", " \t], \n", " \n", + " \tattachments = [], \n", + " \n", " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 17:08:20.345033, \n", + " \tcreated_at = 2024-03-19 14:20:27.540398, \n", " \n", - " \tupdated_at = 2024-03-05 17:08:20.345033, \n", + " \tupdated_at = 2024-03-19 14:20:27.540398, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=107, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT']), \n", - " \t\tApp(kitem_app_id=108, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", - " \thdf5 = [\n", - " \t\tColumn(column_id=0, name=Prüfzeit), \n", - " \t\tColumn(column_id=1, name=Standardkraft), \n", - " \t\tColumn(column_id=2, name=Traversenweg absolut), \n", - " \t\tColumn(column_id=3, name=Standardweg), \n", - " \t\tColumn(column_id=4, name=Breitenänderung), \n", - " \t\tColumn(column_id=5, name=Dehnung)\n", - " \t]\n", - " ),\n", - " KItem(\n", + " \thdf5 = None\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_tensile_test_f2, \n", + " \tname = csv_test, \n", " \n", - " \tid = c9203684-f1ce-4d23-92f9-478a002b7440, \n", + " \tid = 99bbb5d3-add5-42b0-9b75-93ee29b01766, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csv_tensile_test_f2, \n", + " \tslug = csvtest, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=DP800_F_FZ2_WR15_97.TXT)\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", " \t], \n", " \n", " \tlinked_kitems = [], \n", @@ -1766,65 +1552,67 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-05 17:08:27.579333, \n", + " \tcreated_at = 2024-04-16 18:32:06.073842, \n", " \n", - " \tupdated_at = 2024-03-05 17:08:27.579333, \n", + " \tupdated_at = 2024-04-16 18:32:06.073842, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=109, executable=csv_tensile_test_f2/csv_tensile_test_f2.ipynb, title=csv_tensile_test_f2, description=csv_tensile_test_f2, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = material constants, \n", + " \tname = foo 1, \n", " \n", - " \tid = ea0bb87b-3a26-48be-84e1-bec9b99a1204, \n", + " \tid = 8e3861e6-3479-411d-a460-addb91459da4, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = material_constants, \n", + " \tslug = foo1-8e3861e6, \n", " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/MaterialCard, name=MaterialCard, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204)\n", + " \t\t{\n", + " \t\t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89,\n", + " \t\t\tsource_id: 8e3861e6-3479-411d-a460-addb91459da4\n", + " \t\t}\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-27 05:31:47.433035, \n", + " \tcreated_at = 2024-04-17 23:31:25.540343, \n", " \n", - " \tupdated_at = 2024-02-27 05:31:47.433035, \n", + " \tupdated_at = 2024-04-17 23:31:25.540343, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1834,46 +1622,44 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={'material constants': {'C11': 226000.0, 'C12': 140000.0, 'C44': 116000.0, 'shear_rate_ref': 0.001, 'rate_sens': 0.0125, 'tau0': 62.1930648, 'tau1': 48.1881715, 'theta0': 356.842243, 'theta1': 37.4360252, 'q1': 1.4, 'q2': 1.4}}), \n", + " \tcustom_properties = {\n", + " \t\tid: 8e3861e6-3479-411d-a460-addb91459da4, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = orientations, \n", + " \tname = foo 3, \n", " \n", - " \tid = 5386bf18-533a-4976-8481-d54638f07a91, \n", + " \tid = 17f703f5-cd63-4575-a440-112b70db774d, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", - " \tslug = orientations, \n", + " \tslug = foo3-17f703f5, \n", " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/GrainOrientation, name=GrainOrientation, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", + " \tannotations = [], \n", " \n", - " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang)\n", - " \t], \n", + " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=5386bf18-533a-4976-8481-d54638f07a91), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=5386bf18-533a-4976-8481-d54638f07a91)\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-27 05:31:47.498209, \n", + " \tcreated_at = 2024-04-17 23:31:27.691193, \n", " \n", - " \tupdated_at = 2024-02-27 05:31:47.498209, \n", + " \tupdated_at = 2024-04-17 23:31:27.691193, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1883,45 +1669,48 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = {\n", + " \t\tid: 17f703f5-cd63-4575-a440-112b70db774d, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = rve_1000_grains_geofile, \n", + " \tname = test, \n", " \n", - " \tid = 13e57827-c5fe-413e-90dd-bb95ec5c1d3a, \n", + " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = rve_1000_grains_geofile, \n", + " \tslug = test, \n", " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/Geometry, name=Geometry, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [\n", - " \t\tAttachment(name=mesh_40x40x40_grains_1000_aspect_ratio_1_6.geo)\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=13e57827-c5fe-413e-90dd-bb95ec5c1d3a)\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", + " \t\t{\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-27 05:31:47.588776, \n", + " \tcreated_at = 2024-04-16 18:33:54.428378, \n", " \n", - " \tupdated_at = 2024-02-27 05:31:47.588776, \n", + " \tupdated_at = 2024-04-16 18:33:54.428378, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1931,39 +1720,72 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = None, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False)]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... or for all of type `DatasetCatalog` with `foo` in the name:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SearchResult(hit=KItem(\n", " \n", - " \tname = foo 3, \n", + " \tname = foo 1, \n", " \n", - " \tid = 17ec4319-7643-40e8-b3d2-a16fbafd9571, \n", + " \tid = 8e3861e6-3479-411d-a460-addb91459da4, \n", " \n", - " \tktype_id = organization, \n", + " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo3, \n", + " \tslug = foo1-8e3861e6, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89,\n", + " \t\t\tsource_id: 8e3861e6-3479-411d-a460-addb91459da4\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-06 10:13:30.805087, \n", + " \tcreated_at = 2024-04-17 23:31:25.540343, \n", " \n", - " \tupdated_at = 2024-03-06 10:13:30.805087, \n", + " \tupdated_at = 2024-04-17 23:31:25.540343, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1973,67 +1795,133 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = {\n", + " \t\tid: 8e3861e6-3479-411d-a460-addb91459da4, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " )]" + " ), fuzzy=False)]" ] }, - "execution_count": 22, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "... or for all of type `DatasetCatalog` with `foo` in the name:" + "... and for all of type `Organization` with the annotation `www.example.org/foo`:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[KItem(\n", + "[SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = Fraunhofer, \n", " \n", - " \tid = 8377f120-d558-4cc2-93d4-582bb01fac11, \n", + " \tid = 6c695c8b-06ec-4df8-b840-d2dc0752e22d, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", - " \tslug = foo1, \n", + " \tslug = fraunhofer, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: http://www.oie.eu/ontology/characterization-method#ElectricalCharacterisationMethods,\n", + " \t\t\tname: ElectricalCharacterisationMethods,\n", + " \t\t\tnamespace: method\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=5dacf60c-6a62-466a-a85e-9f3ce422cefa, source_id=8377f120-d558-4cc2-93d4-582bb01fac11)\n", + " \t\t{\n", + " \t\t\tid: 2539dfde-abc9-4fdd-9204-026252ee952c,\n", + " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b23df795-9441-4a0d-993a-aaeee53c4164,\n", + " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 0f995978-9932-436a-9d76-961463457ed2,\n", + " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t}\n", + " \t], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\t{\n", + " \t\t\tuser_id: 8d01f084-3604-4956-bd42-a09610e71d0e\n", + " \t\t}\n", " \t], \n", " \n", + " \tavatar_exists = True, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-02-13 15:20:23.000643, \n", + " \n", + " \tupdated_at = 2024-02-13 15:20:23.000643, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", + " \n", + " \tname = foo 3, \n", + " \n", + " \tid = 17f703f5-cd63-4575-a440-112b70db774d, \n", + " \n", + " \tktype_id = organization, \n", + " \n", + " \tslug = foo3-17f703f5, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-06 10:13:29.213578, \n", + " \tcreated_at = 2024-04-17 23:31:27.691193, \n", " \n", - " \tupdated_at = 2024-03-06 10:13:29.213578, \n", + " \tupdated_at = 2024-04-17 23:31:27.691193, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2043,69 +1931,55 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = {\n", + " \t\tid: 17f703f5-cd63-4575-a440-112b70db774d, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... and for all of type `Organization` with the annotation `www.example.org/foo`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", " \tname = foo 2, \n", " \n", - " \tid = 5dacf60c-6a62-466a-a85e-9f3ce422cefa, \n", + " \tid = dd54b41e-99fb-428e-a1f3-4812d0207e89, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo2, \n", + " \tslug = foo2-dd54b41e, \n", " \n", " \tannotations = [\n", - " \t\tAnnotation(iri=www.example.org/foo, name=foo, namespace=www.example.org)\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}\n", " \t], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=8377f120-d558-4cc2-93d4-582bb01fac11, source_id=5dacf60c-6a62-466a-a85e-9f3ce422cefa)\n", + " \t\t{\n", + " \t\t\tid: 8e3861e6-3479-411d-a460-addb91459da4,\n", + " \t\t\tsource_id: dd54b41e-99fb-428e-a1f3-4812d0207e89\n", + " \t\t}\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-06 10:13:29.996514, \n", + " \tcreated_at = 2024-04-17 23:31:26.613092, \n", " \n", - " \tupdated_at = 2024-03-06 10:13:29.996514, \n", + " \tupdated_at = 2024-04-17 23:31:26.613092, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2115,22 +1989,29 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = {\n", + " \t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " ),\n", - " KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", " \tname = foo 4, \n", " \n", - " \tid = 6e0574de-e358-4305-9148-244132506044, \n", + " \tid = 4437eab7-2fa3-4338-a617-0d62e50cdd1c, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo4, \n", + " \tslug = foo4-4437eab7, \n", " \n", " \tannotations = [\n", - " \t\tAnnotation(iri=www.example.org/bar, name=bar, namespace=https://www.example.org)\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: https://www.example.org\n", + " \t\t}\n", " \t], \n", " \n", " \tattachments = [], \n", @@ -2140,16 +2021,18 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-06 10:13:31.643039, \n", + " \tcreated_at = 2024-04-17 23:31:29.090119, \n", " \n", - " \tupdated_at = 2024-03-06 10:13:31.643039, \n", + " \tupdated_at = 2024-04-17 23:31:29.090119, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2159,13 +2042,16 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={}), \n", + " \tcustom_properties = {\n", + " \t\tid: 4437eab7-2fa3-4338-a617-0d62e50cdd1c, \n", + " \t\tcontent: {}\n", + " \t}, \n", " \n", " \thdf5 = None\n", - " )]" + " ), fuzzy=False)]" ] }, - "execution_count": 24, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -2185,29 +2071,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "del dsms[item]\n", "del dsms[item2]\n", "del dsms[item3]\n", "del dsms[item4]\n", "\n", - "dsms.commit()\n", - "\n", - "dsms.kitems" + "dsms.commit()\n" ] }, { @@ -2226,21 +2099,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[App(filename='bulk_clone_pipelines.ipynb', basename='bulk_clone_pipelines.ipynb', folder=''),\n", + "[App(filename='csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='csv_bulgetest'),\n", " App(filename='csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='csv_tensile_test'),\n", " App(filename='csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='csv_tensile_test_f2'),\n", + " App(filename='excel_component_test/excel_component_test.ipynb', basename='excel_component_test.ipynb', folder='excel_component_test'),\n", " App(filename='excel_nakajima_test/excel_nakajima_test.ipynb', basename='excel_nakajima_test.ipynb', folder='excel_nakajima_test'),\n", - " App(filename='excel_shear_test/excel_shear_tensile_test.ipynb', basename='excel_shear_tensile_test.ipynb', folder='excel_shear_test'),\n", - " App(filename='excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='excel_tensile_test')]" + " App(filename='excel_notch_test/excel_notch_tensile_test.ipynb', basename='excel_notch_tensile_test.ipynb', folder='excel_notch_test'),\n", + " App(filename='excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='excel_tensile_test'),\n", + " App(filename='tensiletest-parameter-identification/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='tensiletest-parameter-identification'),\n", + " App(filename='tensiletest-parameter-identification/tensiletest_parameter_identification_voila.ipynb', basename='tensiletest_parameter_identification_voila.ipynb', folder='tensiletest-parameter-identification'),\n", + " App(filename='tensiletest-parameter-identification/voila_test.ipynb', basename='voila_test.ipynb', folder='tensiletest-parameter-identification')]" ] }, - "execution_count": 26, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -2265,7 +2142,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -2319,7 +2196,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -2328,7 +2205,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -2352,7 +2229,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.12.2" } }, "nbformat": 4, From fde43b6f74fda02881c5742cf1a9670211a93b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 18 Apr 2024 09:52:11 +0200 Subject: [PATCH 031/114] Bump version v1.4.0rc2 -> v1.4.0rc3 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b1777d3..08fb103 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc2 +version = v1.4.0rc3 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From a0fcaf7e1262dce21741cac7afe129f4521f0c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 01:48:38 +0200 Subject: [PATCH 032/114] debug custom properties --- dsms/knowledge/search.py | 8 +++++--- dsms/knowledge/utils.py | 20 +++++++++++--------- examples/unit.py | 11 ----------- 3 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 examples/unit.py diff --git a/dsms/knowledge/search.py b/dsms/knowledge/search.py index 5b7f8b6..42b1e8d 100644 --- a/dsms/knowledge/search.py +++ b/dsms/knowledge/search.py @@ -1,6 +1,6 @@ """DSMS search model""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from pydantic import BaseModel, Field @@ -12,6 +12,8 @@ class SearchResult(BaseModel): """DSMS search result""" hit: "KItem" = Field(..., description="KItem returned by the search") - fuzzy: bool = Field( - ..., description="Whether the KItem was found through a similarity hit" + fuzzy: Union[bool, float] = Field( + ..., + description="""Whether the KItem was found through a similarity hit. + If not a bool, a float indicates the distance from search term""", ) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 79d7dfd..17d23b8 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -55,24 +55,25 @@ def _create_custom_properties_model( default = form_input.get("defaultValue") slug = _slugify(label) if dtype in ("Text", "File", "Textarea", "Vocabulary term"): - dtype = str + dtype = Optional[str] elif dtype in ("Number", "Slider"): - dtype = NumericalDataType + dtype = Optional[NumericalDataType] elif dtype == "Checkbox": - dtype = bool + dtype = Optional[bool] elif dtype in ("Select", "Radio"): - dtype = Enum( + choices = Enum( _name_to_camel(label) + "Choices", { _name_to_camel(choice["value"]): choice["value"] for choice in form_input.get("choices") }, ) + dtype = Optional[choices] elif dtype == "Knowledge item": warnings.warn( "knowledge item not fully supported for KTypes yet." ) - dtype = str + dtype = Optional[str] fields[slug] = (dtype, default or None) fields["kitem"] = ( @@ -264,16 +265,17 @@ def _update_kitem(kitem: "KItem") -> Response: }, exclude_none=True, ) - custom_properties = kitem.custom_properties.model_dump() payload = json.loads(dumped) - payload.update( - custom_properties={"content": custom_properties}, **differences - ) payload.update( external_links={ link.label: str(link.url) for link in kitem.external_links } ) + if kitem.custom_properties: + custom_properties = kitem.custom_properties.model_dump() + payload.update( + custom_properties={"content": custom_properties}, **differences + ) response = _perform_request( f"api/knowledge/kitems/{kitem.id}", "put", json=payload ) diff --git a/examples/unit.py b/examples/unit.py deleted file mode 100644 index 94f1c3b..0000000 --- a/examples/unit.py +++ /dev/null @@ -1,11 +0,0 @@ -from dsms import DSMS - -dsms = DSMS(env="../.env") - -item = dsms.search(query="AFZ1-Fz-S1Q", allow_fuzzy=False, limit=1)[0].hit - -print(item) - -print(item.custom_properties.OriginalWidth.convert_to("m")) - -print(item.hdf5.PercentageExtension.get()[:100]) From 54178084a3af5cee3006a05974eab7c3c5cd7dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 02:01:30 +0200 Subject: [PATCH 033/114] set default data for custom properties --- dsms/knowledge/kitem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 43e4b73..a52f535 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -160,7 +160,7 @@ class KItem(BaseModel): description="User groups able to access the KItem.", ) custom_properties: Optional[Any] = Field( - {}, description="Custom properties associated to the KItem" + None, description="Custom properties associated to the KItem" ) ktype: Optional[KType] = Field( None, description="KType of the KItem", exclude=True From 9a3ac0f24e4cfc832081f09453a41ef9b762c6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 02:02:26 +0200 Subject: [PATCH 034/114] Bump version v1.4.0rc3 -> v1.4.0rc4 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 08fb103..1350c1b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc3 +version = v1.4.0rc4 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From a5ed1da431e59425da2284175e926f08d783c330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 02:20:54 +0200 Subject: [PATCH 035/114] debug kitem update --- dsms/knowledge/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 17d23b8..c74f2bb 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -269,13 +269,12 @@ def _update_kitem(kitem: "KItem") -> Response: payload.update( external_links={ link.label: str(link.url) for link in kitem.external_links - } + }, + **differences, ) if kitem.custom_properties: custom_properties = kitem.custom_properties.model_dump() - payload.update( - custom_properties={"content": custom_properties}, **differences - ) + payload.update(custom_properties={"content": custom_properties}) response = _perform_request( f"api/knowledge/kitems/{kitem.id}", "put", json=payload ) From 26bc37a0d599e23e6d502089492696cb5b2f7ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 02:21:27 +0200 Subject: [PATCH 036/114] Bump version v1.4.0rc4 -> v1.4.0rc5 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1350c1b..99349a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc4 +version = v1.4.0rc5 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 6628e6c58e6f962356f278d06e14ed61029d0af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 03:01:09 +0200 Subject: [PATCH 037/114] update default knowledge-item repo name in triplestore --- dsms/core/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 47551e7..ad8ec98 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -69,7 +69,7 @@ class Configuration(BaseSettings): ) kitem_repo: str = Field( - "knowledge", + "knowledge-items", description="Repository of the triplestore for KItems in the DSMS", ) From 47fd8dae1f8119db27124aca53eff0be9caf1a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 03:05:08 +0200 Subject: [PATCH 038/114] Bump version v1.4.0rc5 -> v1.4.0rc6 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 99349a1..2df25ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc5 +version = v1.4.0rc6 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 36273fafc8f2bd503319f870f49f7c1f71ed95a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 03:23:17 +0200 Subject: [PATCH 039/114] further debug default triplestore repo --- dsms/core/configuration.py | 3 ++- dsms/knowledge/sparql_interface/sparql_interface.py | 7 ++++--- dsms/knowledge/sparql_interface/subgraph.py | 9 +++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index ad8ec98..1d7b23f 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -16,6 +16,7 @@ MODULE_REGEX = r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*:[a-zA-Z_][a-zA-Z0-9_]*$" DEFAULT_UNIT_SPARQL = "dsms.knowledge.semantics.units.sparql:UnitSparqlQuery" +DEFAULT_REPO = "knowledge-items" class Configuration(BaseSettings): @@ -69,7 +70,7 @@ class Configuration(BaseSettings): ) kitem_repo: str = Field( - "knowledge-items", + DEFAULT_REPO, description="Repository of the triplestore for KItems in the DSMS", ) diff --git a/dsms/knowledge/sparql_interface/sparql_interface.py b/dsms/knowledge/sparql_interface/sparql_interface.py index f3073bf..118c31d 100644 --- a/dsms/knowledge/sparql_interface/sparql_interface.py +++ b/dsms/knowledge/sparql_interface/sparql_interface.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from dsms.core.configuration import DEFAULT_REPO from dsms.knowledge.sparql_interface.subgraph import Subgraph from dsms.knowledge.sparql_interface.utils import ( _add_rdf, @@ -25,7 +26,7 @@ def __init__(self, dsms): self._subgraph = Subgraph(dsms) def query( - self, query: str, repository: str = "knowledge" + self, query: str, repository: str = DEFAULT_REPO ) -> "Dict[str, Any]": """Perform Sparql Query""" return _sparql_query(query, repository) @@ -33,7 +34,7 @@ def query( def update( self, file_or_pathlike: "Union[str, TextIO]", - repository: str = "knowledge", + repository: str = DEFAULT_REPO, ) -> None: """Perform update query from local file""" _sparql_update( @@ -43,7 +44,7 @@ def update( def insert( self, file_or_pathlike: "Union[str, TextIO]", - repository: str = "knowledge", + repository: str = DEFAULT_REPO, ) -> None: """Upload RDF to triplestore from local file""" _add_rdf(file_or_pathlike, self._dsms.config.encoding, repository) diff --git a/dsms/knowledge/sparql_interface/subgraph.py b/dsms/knowledge/sparql_interface/subgraph.py index 9be0f23..66819c1 100644 --- a/dsms/knowledge/sparql_interface/subgraph.py +++ b/dsms/knowledge/sparql_interface/subgraph.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from dsms.core.configuration import DEFAULT_REPO from dsms.knowledge.sparql_interface.utils import ( _create_subgraph, _delete_subgraph, @@ -22,22 +23,22 @@ def __init__(self, dsms): """Initalize the Sparql interface""" self._dsms: "DSMS" = dsms - def update(self, graph: "Graph", repository: str = "knowledge") -> None: + def update(self, graph: "Graph", repository: str = DEFAULT_REPO) -> None: """Update a subgraph in the DSMS""" _update_subgraph(graph, self._dsms.config.encoding, repository) - def create(self, graph: "Graph", repository: str = "knowledge") -> None: + def create(self, graph: "Graph", repository: str = DEFAULT_REPO) -> None: """Create a subgraph in the DSMS""" _create_subgraph(graph, self._dsms.config.encoding, repository) - def delete(self, identifier: str, repository: str = "knowledge") -> None: + def delete(self, identifier: str, repository: str = DEFAULT_REPO) -> None: """Delete a subgraph in the DSMS""" _delete_subgraph(identifier, repository) def get( self, identifier: str, - repository: str = "knowledge", + repository: str = DEFAULT_REPO, is_kitem_id: bool = False, ) -> "Graph": """Get a subgraph from the DSMS""" From 5ebba7686edcde2ed862b5488773d1e9755e0ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 19 Apr 2024 03:23:32 +0200 Subject: [PATCH 040/114] Bump version v1.4.0rc6 -> v1.4.0rc7 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2df25ff..4d95dca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc6 +version = v1.4.0rc7 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 93ec340b90cd6ad57ae3d1f54530316014cab916 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Fri, 19 Apr 2024 16:02:02 +0200 Subject: [PATCH 041/114] Synching with remote repo --- .vscode/settings.json | 3 + docs/describe.md | 44 + docs/src/Makefile | 20 + docs/src/dsms.apps.rst | 29 + docs/src/dsms.core.rst | 45 + docs/src/dsms.knowledge.properties.rst | 117 + docs/src/dsms.knowledge.rst | 46 + docs/src/dsms.knowledge.sparql_interface.rst | 37 + docs/src/dsms.rst | 20 + docs/src/make.bat | 35 + docs/src/modules.rst | 9 + docs/src/setup.rst | 7 + docs/src/source/conf.py | 28 + docs/src/source/index.rst | 20 + docs/src/tests.rst | 37 + dsms/core/dsms.py | 2 +- environment_hiwi.txt | 173 ++ examples/Chapters/1_introduction.ipynb | 139 ++ examples/Chapters/2_creation.ipynb | 168 ++ examples/Chapters/3_updation.ipynb | 171 ++ examples/Chapters/4_deletion.ipynb | 219 ++ examples/Chapters/5_search.ipynb | 248 ++ examples/Chapters/6_app_HDF5.ipynb | 182 ++ examples/basic_usage.ipynb | 2308 +----------------- requirements_PCL590_thesis.txt | 265 ++ 25 files changed, 2132 insertions(+), 2240 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 docs/describe.md create mode 100644 docs/src/Makefile create mode 100644 docs/src/dsms.apps.rst create mode 100644 docs/src/dsms.core.rst create mode 100644 docs/src/dsms.knowledge.properties.rst create mode 100644 docs/src/dsms.knowledge.rst create mode 100644 docs/src/dsms.knowledge.sparql_interface.rst create mode 100644 docs/src/dsms.rst create mode 100644 docs/src/make.bat create mode 100644 docs/src/modules.rst create mode 100644 docs/src/setup.rst create mode 100644 docs/src/source/conf.py create mode 100644 docs/src/source/index.rst create mode 100644 docs/src/tests.rst create mode 100644 environment_hiwi.txt create mode 100644 examples/Chapters/1_introduction.ipynb create mode 100644 examples/Chapters/2_creation.ipynb create mode 100644 examples/Chapters/3_updation.ipynb create mode 100644 examples/Chapters/4_deletion.ipynb create mode 100644 examples/Chapters/5_search.ipynb create mode 100644 examples/Chapters/6_app_HDF5.ipynb create mode 100644 requirements_PCL590_thesis.txt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..458faca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "livePreview.defaultPreviewPath": "/docs/src/build/html/index.html" +} \ No newline at end of file diff --git a/docs/describe.md b/docs/describe.md new file mode 100644 index 0000000..52d2abc --- /dev/null +++ b/docs/describe.md @@ -0,0 +1,44 @@ +""" +This code is part of the dsms-python-sdk package located at D:\HiWi\dsms-python-sdk\dsms. + +The dsms-python-sdk package provides a set of tools and utilities for interacting with the DSMS (Data Science Management System) platform. It offers functionality for data ingestion, data processing, model training, and model deployment. + +The package is organized into the following packages: + +- `dsms.core`: Contains the core functionality of the DSMS SDK, including authentication, API client, and utility functions. +- `dsms.data`: Provides classes and functions for data ingestion and preprocessing, such as data loaders, transformers, and validators. +- `dsms.models`: Includes classes and functions for model training and evaluation, including model builders, trainers, and evaluators. +- `dsms.deploy`: Offers functionality for deploying trained models to the DSMS platform, including model packaging, versioning, and deployment management. + +Please refer to the individual package documentation for more details on their usage and available functionalities. +""" +""" +This module contains the implementation of the dsms-python-sdk package. + +The dsms-python-sdk package provides a set of tools and utilities for interacting with the DSMS (Data Science Management System) platform. + +Usage: + import dsms + + # Example usage + client = dsms.Client() + client.login(username='user', password='pass') + project = client.get_project(project_id='12345') + ... + +For more information, please refer to the official documentation at https://github.com/username/dsms-python-sdk. +""" +# FILEPATH + +def calculate_sum(a, b): + """ + Calculates the sum of two numbers. + + Parameters: + a (int): The first number. + b (int): The second number. + + Returns: + int: The sum of the two numbers. + """ + return a + b diff --git a/docs/src/Makefile b/docs/src/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/src/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/src/dsms.apps.rst b/docs/src/dsms.apps.rst new file mode 100644 index 0000000..b59f2fe --- /dev/null +++ b/docs/src/dsms.apps.rst @@ -0,0 +1,29 @@ +dsms.apps package +================= + +Submodules +---------- + +dsms.apps.apps module +--------------------- + +.. automodule:: dsms.apps.apps + :members: + :undoc-members: + :show-inheritance: + +dsms.apps.utils module +---------------------- + +.. automodule:: dsms.apps.utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: dsms.apps + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/dsms.core.rst b/docs/src/dsms.core.rst new file mode 100644 index 0000000..df0223c --- /dev/null +++ b/docs/src/dsms.core.rst @@ -0,0 +1,45 @@ +dsms.core package +================= + +Submodules +---------- + +dsms.core.configuration module +------------------------------ + +.. automodule:: dsms.core.configuration + :members: + :undoc-members: + :show-inheritance: + +dsms.core.context module +------------------------ + +.. automodule:: dsms.core.context + :members: + :undoc-members: + :show-inheritance: + +dsms.core.dsms module +--------------------- + +.. automodule:: dsms.core.dsms + :members: + :undoc-members: + :show-inheritance: + +dsms.core.utils module +---------------------- + +.. automodule:: dsms.core.utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: dsms.core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/dsms.knowledge.properties.rst b/docs/src/dsms.knowledge.properties.rst new file mode 100644 index 0000000..7a8a8fb --- /dev/null +++ b/docs/src/dsms.knowledge.properties.rst @@ -0,0 +1,117 @@ +dsms.knowledge.properties package +================================= + +Submodules +---------- + +dsms.knowledge.properties.affiliations module +--------------------------------------------- + +.. automodule:: dsms.knowledge.properties.affiliations + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.annotations module +-------------------------------------------- + +.. automodule:: dsms.knowledge.properties.annotations + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.apps module +------------------------------------- + +.. automodule:: dsms.knowledge.properties.apps + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.attachments module +-------------------------------------------- + +.. automodule:: dsms.knowledge.properties.attachments + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.authors module +---------------------------------------- + +.. automodule:: dsms.knowledge.properties.authors + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.base module +------------------------------------- + +.. automodule:: dsms.knowledge.properties.base + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.contacts module +----------------------------------------- + +.. automodule:: dsms.knowledge.properties.contacts + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.custom\_properties module +--------------------------------------------------- + +.. automodule:: dsms.knowledge.properties.custom_properties + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.external\_links module +------------------------------------------------ + +.. automodule:: dsms.knowledge.properties.external_links + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.hdf5 module +------------------------------------- + +.. automodule:: dsms.knowledge.properties.hdf5 + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.linked\_kitems module +----------------------------------------------- + +.. automodule:: dsms.knowledge.properties.linked_kitems + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.summary module +---------------------------------------- + +.. automodule:: dsms.knowledge.properties.summary + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.properties.user\_groups module +--------------------------------------------- + +.. automodule:: dsms.knowledge.properties.user_groups + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: dsms.knowledge.properties + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/dsms.knowledge.rst b/docs/src/dsms.knowledge.rst new file mode 100644 index 0000000..dd17c16 --- /dev/null +++ b/docs/src/dsms.knowledge.rst @@ -0,0 +1,46 @@ +dsms.knowledge package +====================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + dsms.knowledge.properties + dsms.knowledge.sparql_interface + +Submodules +---------- + +dsms.knowledge.kitem module +--------------------------- + +.. automodule:: dsms.knowledge.kitem + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.ktype module +--------------------------- + +.. automodule:: dsms.knowledge.ktype + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.utils module +--------------------------- + +.. automodule:: dsms.knowledge.utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: dsms.knowledge + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/dsms.knowledge.sparql_interface.rst b/docs/src/dsms.knowledge.sparql_interface.rst new file mode 100644 index 0000000..47dc308 --- /dev/null +++ b/docs/src/dsms.knowledge.sparql_interface.rst @@ -0,0 +1,37 @@ +dsms.knowledge.sparql\_interface package +======================================== + +Submodules +---------- + +dsms.knowledge.sparql\_interface.sparql\_interface module +--------------------------------------------------------- + +.. automodule:: dsms.knowledge.sparql_interface.sparql_interface + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.sparql\_interface.subgraph module +------------------------------------------------ + +.. automodule:: dsms.knowledge.sparql_interface.subgraph + :members: + :undoc-members: + :show-inheritance: + +dsms.knowledge.sparql\_interface.utils module +--------------------------------------------- + +.. automodule:: dsms.knowledge.sparql_interface.utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: dsms.knowledge.sparql_interface + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/dsms.rst b/docs/src/dsms.rst new file mode 100644 index 0000000..236d62f --- /dev/null +++ b/docs/src/dsms.rst @@ -0,0 +1,20 @@ +dsms package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + dsms.apps + dsms.core + dsms.knowledge + +Module contents +--------------- + +.. automodule:: dsms + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/make.bat b/docs/src/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/src/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/src/modules.rst b/docs/src/modules.rst new file mode 100644 index 0000000..1d09d62 --- /dev/null +++ b/docs/src/modules.rst @@ -0,0 +1,9 @@ +dsms-python-sdk +=============== + +.. toctree:: + :maxdepth: 4 + + dsms + setup + tests diff --git a/docs/src/setup.rst b/docs/src/setup.rst new file mode 100644 index 0000000..552eb49 --- /dev/null +++ b/docs/src/setup.rst @@ -0,0 +1,7 @@ +setup module +============ + +.. automodule:: setup + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/source/conf.py b/docs/src/source/conf.py new file mode 100644 index 0000000..134b313 --- /dev/null +++ b/docs/src/source/conf.py @@ -0,0 +1,28 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'DSMS-SDK-Documentation' +copyright = '2024, Priyabrat Mishra' +author = 'Priyabrat Mishra' +release = '1.0.0' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' +html_static_path = ['_static'] diff --git a/docs/src/source/index.rst b/docs/src/source/index.rst new file mode 100644 index 0000000..1cdad64 --- /dev/null +++ b/docs/src/source/index.rst @@ -0,0 +1,20 @@ +.. DSMS-SDK-Documentation documentation master file, created by + sphinx-quickstart on Thu Apr 11 19:52:57 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to DSMS-SDK-Documentation's documentation! +================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/src/tests.rst b/docs/src/tests.rst new file mode 100644 index 0000000..ba28f63 --- /dev/null +++ b/docs/src/tests.rst @@ -0,0 +1,37 @@ +tests package +============= + +Submodules +---------- + +tests.conftest module +--------------------- + +.. automodule:: tests.conftest + :members: + :undoc-members: + :show-inheritance: + +tests.test\_kitem module +------------------------ + +.. automodule:: tests.test_kitem + :members: + :undoc-members: + :show-inheritance: + +tests.test\_utils module +------------------------ + +.. automodule:: tests.test_utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tests + :members: + :undoc-members: + :show-inheritance: diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index 68511e2..1b1eafc 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -128,7 +128,7 @@ def headers(cls) -> Dict[str, Any]: @property def kitems(cls) -> "List[KItem]": - """KItems instanciated and available in the remote backend. + """KItems instantiated and available in the remote backend. WARNING: This will download _all_ KItems in the backend owned by the current user and may resolve into long response times. The default timeout for requests is defined under the diff --git a/environment_hiwi.txt b/environment_hiwi.txt new file mode 100644 index 0000000..26b5aaf --- /dev/null +++ b/environment_hiwi.txt @@ -0,0 +1,173 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: win-64 +@EXPLICIT +https://repo.anaconda.com/pkgs/main/win-64/blas-1.0-mkl.conda +https://repo.anaconda.com/pkgs/main/win-64/ca-certificates-2024.3.11-haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/icc_rt-2022.1.0-h6049295_2.conda +https://repo.anaconda.com/pkgs/main/win-64/vs2015_runtime-14.27.29016-h5e58377_2.conda +https://repo.anaconda.com/pkgs/main/win-64/winpty-0.4.3-4.conda +https://repo.anaconda.com/pkgs/main/win-64/vc-14.2-h21ff451_1.conda +https://repo.anaconda.com/pkgs/main/win-64/icu-73.1-h6c2663c_0.conda +https://repo.anaconda.com/pkgs/main/win-64/intel-openmp-2023.1.0-h59b6b97_46320.conda +https://repo.anaconda.com/pkgs/main/win-64/jpeg-9e-h2bbff1b_1.conda +https://repo.anaconda.com/pkgs/main/win-64/lerc-3.0-hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/libbrotlicommon-1.0.9-h2bbff1b_7.conda +https://repo.anaconda.com/pkgs/main/win-64/libdeflate-1.17-h2bbff1b_1.conda +https://repo.anaconda.com/pkgs/main/win-64/libffi-3.4.4-hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/libsodium-1.0.18-h62dcd97_0.conda +https://repo.anaconda.com/pkgs/main/win-64/libwebp-base-1.3.2-h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/lz4-c-1.9.4-h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/openssl-1.1.1w-h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/sqlite-3.41.2-h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/tbb-2021.8.0-h59b6b97_0.conda +https://repo.anaconda.com/pkgs/main/win-64/xz-5.4.6-h8cc25b3_0.conda +https://repo.anaconda.com/pkgs/main/win-64/yaml-0.2.5-he774522_0.conda +https://repo.anaconda.com/pkgs/main/win-64/zlib-1.2.13-h8cc25b3_0.conda +https://repo.anaconda.com/pkgs/main/win-64/krb5-1.20.1-h5b6d351_1.conda +https://repo.anaconda.com/pkgs/main/win-64/libbrotlidec-1.0.9-h2bbff1b_7.conda +https://repo.anaconda.com/pkgs/main/win-64/libbrotlienc-1.0.9-h2bbff1b_7.conda +https://repo.anaconda.com/pkgs/main/win-64/libclang13-14.0.6-default_h8e68704_1.conda +https://repo.anaconda.com/pkgs/main/win-64/libpng-1.6.39-h8cc25b3_0.conda +https://repo.anaconda.com/pkgs/main/win-64/mkl-2023.1.0-h6b88ed4_46358.conda +https://repo.anaconda.com/pkgs/main/win-64/python-3.8.18-h6244533_0.conda +https://repo.anaconda.com/pkgs/main/win-64/zeromq-4.3.5-hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/zstd-1.5.5-hd43e919_0.conda +https://repo.anaconda.com/pkgs/main/win-64/attrs-23.1.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/backcall-0.2.0-pyhd3eb1b0_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/brotli-bin-1.0.9-h2bbff1b_7.conda +https://repo.anaconda.com/pkgs/main/win-64/brotli-python-1.0.9-py38hd77b12b_7.conda +https://repo.anaconda.com/pkgs/main/win-64/certifi-2024.2.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/colorama-0.4.6-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/cycler-0.11.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/debugpy-1.6.7-py38hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/noarch/decorator-5.1.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/defusedxml-0.7.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/exceptiongroup-1.2.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/executing-0.8.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/freetype-2.12.1-ha860e81_0.conda +https://repo.anaconda.com/pkgs/main/win-64/idna-3.4-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/json5-0.9.6-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyterlab_widgets-3.0.10-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/kiwisolver-1.4.4-py38hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/libclang-14.0.6-default_hb5a9fac_1.conda +https://repo.anaconda.com/pkgs/main/win-64/libpq-12.15-hb652d5d_1.conda +https://repo.anaconda.com/pkgs/main/win-64/libtiff-4.5.1-hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/markupsafe-2.1.3-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/mistune-2.0.4-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/mkl-service-2.4.0-py38h2bbff1b_1.conda +https://repo.anaconda.com/pkgs/main/noarch/munkres-1.1.4-py_0.conda +https://repo.anaconda.com/pkgs/main/win-64/nest-asyncio-1.6.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/overrides-7.4.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/packaging-23.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pandocfilters-1.5.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/parso-0.8.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pickleshare-0.7.5-pyhd3eb1b0_1003.conda +https://repo.anaconda.com/pkgs/main/win-64/pkgutil-resolve-name-1.3.10-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/platformdirs-3.10.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/ply-3.11-py38_0.conda +https://repo.anaconda.com/pkgs/main/win-64/prometheus_client-0.14.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/psutil-5.9.0-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pure_eval-0.2.2-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pycparser-2.21-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pygments-2.15.1-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/pyparsing-3.0.9-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pyqt5-sip-12.13.0-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/python-fastjsonschema-2.16.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/python-json-logger-2.0.7-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/python-tzdata-2023.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pytz-2023.3.post1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pywin32-305-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pywinpty-2.0.10-py38h5da7b33_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pyyaml-6.0.1-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pyzmq-25.1.2-py38hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/rfc3986-validator-0.1.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/rpds-py-0.10.6-py38h062c2fa_0.conda +https://repo.anaconda.com/pkgs/main/win-64/send2trash-1.8.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/setuptools-68.2.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/six-1.16.0-pyhd3eb1b0_1.conda +https://repo.anaconda.com/pkgs/main/win-64/sniffio-1.3.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/soupsieve-2.5-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/tomli-2.0.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/tornado-6.3.3-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/traitlets-5.7.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/typing_extensions-4.9.0-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/noarch/wcwidth-0.2.5-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/webencodings-0.5.1-py38_1.conda +https://repo.anaconda.com/pkgs/main/win-64/wheel-0.41.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/widgetsnbextension-4.0.10-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/win_inet_pton-1.1.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/zipp-3.17.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/anyio-4.2.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/asttokens-2.0.5-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/async-lru-2.0.4-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/babel-2.11.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/beautifulsoup4-4.12.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/bleach-4.1.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/brotli-1.0.9-h2bbff1b_7.conda +https://repo.anaconda.com/pkgs/main/win-64/cffi-1.16.0-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/comm-0.2.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/importlib-metadata-7.0.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/importlib_resources-6.1.1-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/jedi-0.18.1-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/jinja2-3.1.3-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter_core-5.5.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/jupyterlab_pygments-0.1.2-py_0.conda +https://repo.anaconda.com/pkgs/main/win-64/matplotlib-inline-0.1.6-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/numpy-base-1.24.3-py38h8a87ada_1.conda +https://repo.anaconda.com/pkgs/main/win-64/openjpeg-2.4.0-h4fc8c34_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pip-23.3.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/prompt-toolkit-3.0.43-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pysocks-1.7.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/noarch/python-dateutil-2.8.2-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/qt-main-5.15.2-he2df7e6_10.conda +https://repo.anaconda.com/pkgs/main/win-64/qtpy-2.4.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/referencing-0.30.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/rfc3339-validator-0.1.4-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/sip-6.7.12-py38hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/terminado-0.17.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/tinycss2-1.2.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/typing-extensions-4.9.0-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/websocket-client-0.58.0-py38haa95532_4.conda +https://repo.anaconda.com/pkgs/main/win-64/argon2-cffi-bindings-21.2.0-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/noarch/fonttools-4.25.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/importlib_metadata-7.0.1-hd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jsonschema-specifications-2023.7.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter_server_terminals-0.4.4-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/pillow-10.2.0-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/noarch/prompt_toolkit-3.0.43-hd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pyqt-5.15.10-py38hd77b12b_0.conda +https://repo.anaconda.com/pkgs/main/noarch/stack_data-0.2.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/urllib3-2.1.0-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/noarch/argon2-cffi-21.3.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/win-64/ipython-8.12.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jsonschema-4.19.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter_client-8.6.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/requests-2.31.0-py38haa95532_1.conda +https://repo.anaconda.com/pkgs/main/win-64/ipykernel-6.28.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/ipywidgets-8.1.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter_events-0.8.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/nbformat-5.9.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pooch-1.7.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter_console-6.6.3-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/nbclient-0.8.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/qtconsole-5.5.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/nbconvert-7.10.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter_server-2.10.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter-lsp-2.2.0-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyterlab_server-2.25.1-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/notebook-shim-0.2.3-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyterlab-4.0.11-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/notebook-7.0.8-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jupyter-1.0.0-py38haa95532_9.conda +https://repo.anaconda.com/pkgs/main/win-64/bottleneck-1.3.7-py38h9128911_0.conda +https://repo.anaconda.com/pkgs/main/win-64/contourpy-1.0.5-py38h59b6b97_0.conda +https://repo.anaconda.com/pkgs/main/win-64/matplotlib-3.7.2-py38haa95532_0.conda +https://repo.anaconda.com/pkgs/main/win-64/matplotlib-base-3.7.2-py38h4ed8f06_0.conda +https://repo.anaconda.com/pkgs/main/win-64/mkl_fft-1.3.8-py38h2bbff1b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/mkl_random-1.2.4-py38h59b6b97_0.conda +https://repo.anaconda.com/pkgs/main/win-64/numpy-1.24.3-py38h79a8e48_1.conda +https://repo.anaconda.com/pkgs/main/win-64/numexpr-2.8.4-py38h7b80656_1.conda +https://repo.anaconda.com/pkgs/main/win-64/scipy-1.10.1-py38hdcfc7df_1.conda +https://repo.anaconda.com/pkgs/main/win-64/pandas-2.0.3-py38h4ed8f06_0.conda diff --git a/examples/Chapters/1_introduction.ipynb b/examples/Chapters/1_introduction.ipynb new file mode 100644 index 0000000..ed9bacd --- /dev/null +++ b/examples/Chapters/1_introduction.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Introduction to the DSMS-SDK\n", + "\n", + "In this tutorial we see the overview on how to setup and basic use DSMS-SDK\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1. Setting up\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2. Introduction to KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see which kind of DSMS-object we own as a user:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate what a KItem needs in order to be created. KItems are entirely based on [`Pydantic`](https://docs.pydantic.dev/latest/)-Models (v2), hence the properties (in `Pydantic` called `Fields`) are automatically validated once we set them. \n", + "\n", + "The schema of the KItem itself is a JSON schema which is machine-readable and can be directly incorporated into [Swagger](https://swagger.io/tools/swagger-ui/)-supported APIs like e.g. [`FastAPI`](https://fastapi.tiangolo.com/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pprint(KItem.model_json_schema())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate the KTypes defined in the remote instance:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for ktype in dsms.ktypes:\n", + " print(ktype)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Chapters/2_creation.ipynb b/examples/Chapters/2_creation.ipynb new file mode 100644 index 0000000..ff25891 --- /dev/null +++ b/examples/Chapters/2_creation.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Create Kitems\n", + "\n", + "In this tutorial we see how to create new Kitems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1: Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2: Create KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can make new KItems by simple class-initiation: (Make Sure existing KItems is not put as input). \n", + "#" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item = KItem(\n", + " name=\"foo123\",\n", + " ktype_id=dsms.ktypes.DatasetCatalog,\n", + " custom_properties={\"foo\": \"bar\"},\n", + ")\n", + "\n", + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Remember: changes are only syncronized with the DSMS when you call the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the object we created before running the `commit`-method has automatically been updated, e.g. with the creation- and update-timestamp:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And the list of our KItems in the DSMS has been updated as well:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Chapters/3_updation.ipynb b/examples/Chapters/3_updation.ipynb new file mode 100644 index 0000000..e5e5339 --- /dev/null +++ b/examples/Chapters/3_updation.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Updating KItems\n", + "\n", + "In this tutorial we see how to update existing Kitems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.1. Setting up\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. Updating Kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we would like to update the properties of our KItem we created previously.\n", + "\n", + "Depending on the schema of each property (see `KItem.model_schema_json()` in the **Introduction** of this tutorial), we can simply use the standard `list`-method as we know them from basic Python (e.g. for the `annotations`, `attachments`, `external_link`, etc). \n", + "\n", + "\n", + "Other properties which are not `list`-like can be simply set by attribute-assignment (e.g. `name`, `slug`, `ktype_id`, etc)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# specify the path to any arbitrary file to be uploaded\n", + "file = os.path.join(\"..\", \"README.md\")\n", + "\n", + "item.name = \"foobar\"\n", + "item.custom_properties.update({\"foobar\": \"foobar\"})\n", + "item.attachments.append({\"name\": file})\n", + "item.annotations.append(\n", + " {\n", + " \"iri\": \"www.example.org/foo\",\n", + " \"name\": \"example class\",\n", + " \"namespace\": \"www.example.org\",\n", + " }\n", + ")\n", + "item.external_links.append(\n", + " {\"url\": \"http://example.org\", \"label\": \"example link\"}\n", + ")\n", + "item.contacts.append({\"name\": \"foo\", \"email\": \"foo@bar.mail\"})\n", + "item.affiliations.append({\"name\": \"foobar team\"})\n", + "item.user_groups.append({\"name\": \"foogroup\", \"group_id\": \"123\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Changes are sent to the DSMS through the `commit`-method again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see now that e.g. the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have beem thrown during the `commit`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Furthermore we can also download the file we uploaded again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for file in item.attachments:\n", + " download = file.download()\n", + "\n", + " print(\"\\t\\t\\t Downloaded file:\", file)\n", + " print(\"|------------------------------------Beginning of file------------------------------------|\")\n", + " print(download)\n", + " print(\"|---------------------------------------End of file---------------------------------------|\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Chapters/4_deletion.ipynb b/examples/Chapters/4_deletion.ipynb new file mode 100644 index 0000000..1edf808 --- /dev/null +++ b/examples/Chapters/4_deletion.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Deleting KItems\n", + "\n", + "In this tutorial we see how to delete new Kitems and their properties." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.1. Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2. Deletion of KItems and their properties" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also remove properties from the KItem without deleting the KItem itself.\n", + "\n", + "For the `list`-like properties, we can use the standard `list`-methods from basic Python again (e.g. `pop`, `remove`, etc. or the `del`-operator).\n", + "\n", + "For the other, non-`list`-like properties, we can simply use the attribute-assignment again.\n", + "\n", + "When we only want single parts of the properties in the KItem, we can do it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item.attachments.pop(0)\n", + "item.annotations.pop(0)\n", + "item.external_links.pop(0)\n", + "item.contacts.pop(0)\n", + "item.user_groups.pop(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we can also reset the entire property by setting it to e.g. an empty list again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item.affiliations = []" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Send the changes to the DSMS with the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we can also delete the whole KItem from the DSMS by applying the `del`-operator to the `dsms`-object with the individual `KItem`-object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Commit the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see know, our KItem has been removed from our inventory:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Chapters/5_search.ipynb b/examples/Chapters/5_search.ipynb new file mode 100644 index 0000000..b8527a8 --- /dev/null +++ b/examples/Chapters/5_search.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 5. Searching Kitems\n", + "\n", + "In this tutorial we see how to search existing Kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.1. Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see which kind of DSMS-object we own as a user:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.2. Searching for KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the last unit of this tutorial, we would like to search for specfic KItems we created in the DSMS.\n", + "\n", + "For this purpose, we will firstly create some KItems and apply the `search`-method on the `DSMS`-object later on in order to find them again in the DSMS.\n", + "\n", + "We also wnat to demonstrate here, that we can link KItems to each other in order to find e.g. a related item of type `DatasetCatalog`. For this strategy, we are using the `linked_kitems`-attribute and the `id` of the item which we would like to link.\n", + "\n", + "The procedure looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item = KItem(\n", + " name=\"foo 1\",\n", + " ktype_id=dsms.ktypes.DatasetCatalog\n", + ")\n", + "\n", + "item2 = KItem(\n", + " name=\"foo 2\",\n", + " ktype_id=dsms.ktypes.Organization,\n", + " linked_kitems=[item],\n", + " annotations=[\n", + " {\n", + " \"iri\": \"www.example.org/foo\",\n", + " \"name\": \"foo\",\n", + " \"namespace\": \"www.example.org\",\n", + " }\n", + " ],\n", + ")\n", + "item3 = KItem(\n", + " name=\"foo 3\", \n", + " ktype_id=dsms.ktypes.Organization\n", + ")\n", + "item4 = KItem(\n", + " name=\"foo 4\",\n", + " ktype_id=dsms.ktypes.Organization,\n", + " annotations=[\n", + " {\n", + " \"iri\": \"www.example.org/bar\",\n", + " \"name\": \"bar\",\n", + " \"namespace\": \"https://www.example.org\",\n", + " }\n", + " ],\n", + ")\n", + "\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we are apply to search for e.g. kitems of type `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` and `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... or for all of type `DatasetCatalog` with `foo` in the name:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` with the annotation `www.example.org/foo`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.search(\n", + " ktypes=[dsms.ktypes.Organization], annotations=[\"www.example.org/foo\"]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Clean up the DSMS from the tutortial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]\n", + "del dsms[item2]\n", + "del dsms[item3]\n", + "del dsms[item4]\n", + "\n", + "dsms.commit()\n", + "\n", + "dsms.kitems" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Chapters/6_app_HDF5.ipynb b/examples/Chapters/6_app_HDF5.ipynb new file mode 100644 index 0000000..9f9368a --- /dev/null +++ b/examples/Chapters/6_app_HDF5.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6. Available apps investigation and HDF5\n", + "\n", + "In this tutorial we see how to :\n", + "\n", + "1.Investigate which apps are available \n", + "\n", + "2.Extract kitems into dataframes via retrieval using hdf5 format." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1: Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pprint import pprint\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#specify path to an arbitrary file\n", + "env = os.path.join(\"..\", \".env\")\n", + "\n", + "# start the session\n", + "load_dotenv(env)\n", + "\n", + "dsms = DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see which kind of DSMS-object we own as a user:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1. Investigating Available Apps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate which apps are available through JupyterLab:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.apps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2. Extraction into Data Frame for further analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are also able to upload dataframes or time series data and investigate them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", + "\n", + "\n", + "item = KItem(name=\"testdata123\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", + "dsms.commit()\n", + "\n", + "print(\"Column-wise:\")\n", + "for column in item.hdf5:\n", + " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n", + "\n", + "df = item.hdf5.to_df()\n", + "print(\"\\nAs data frame:\")\n", + "print(df)\n", + "\n", + "new_df = df.drop(['a'], axis=1)\n", + "item.hdf5 = new_df\n", + "\n", + "dsms.commit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 53575f5..11e0848 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -9,6 +9,15 @@ "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# pending\n", + "\n", + "Also make sure to pip install dsms-sdk and how to export DSMS_HOST_URL and DSMS_TOKEN, attach." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -18,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -39,9 +48,21 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", + "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", + "\u001b[1;31mClick here for more info. \n", + "\u001b[1;31mView Jupyter log for further details." + ] + } + ], "source": [ "#specify path to an arbitrary file\n", "env = os.path.join(\"..\", \".env\")\n", @@ -68,20 +89,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.kitems" ] @@ -97,630 +107,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'$defs': {'AdditionalProperties': {'description': 'Additional properties of',\n", - " 'properties': {'triggerUponUpload': {'default': False,\n", - " 'description': 'Whether '\n", - " 'the '\n", - " 'app '\n", - " 'should '\n", - " 'be '\n", - " 'triggered '\n", - " 'when '\n", - " 'a '\n", - " 'file '\n", - " 'is '\n", - " 'uploaded',\n", - " 'title': 'Triggeruponupload',\n", - " 'type': 'boolean'},\n", - " 'triggerUponUploadFileExtensions': {'anyOf': [{'items': {'type': 'string'},\n", - " 'type': 'array'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'File '\n", - " 'extensions '\n", - " 'for '\n", - " 'which '\n", - " 'the '\n", - " 'upload '\n", - " 'shall '\n", - " 'be '\n", - " 'triggered.',\n", - " 'title': 'Triggeruponuploadfileextensions'}},\n", - " 'title': 'AdditionalProperties',\n", - " 'type': 'object'},\n", - " 'Affiliation': {'description': 'Affiliation of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'affiliation',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name'],\n", - " 'title': 'Affiliation',\n", - " 'type': 'object'},\n", - " 'Annotation': {'description': 'KItem annotation model',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'iri': {'description': 'IRI of the '\n", - " 'annotation',\n", - " 'title': 'Iri',\n", - " 'type': 'string'},\n", - " 'name': {'description': 'Name of the '\n", - " 'annotation',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'namespace': {'description': 'Namespace '\n", - " 'of the '\n", - " 'annotation',\n", - " 'title': 'Namespace',\n", - " 'type': 'string'}},\n", - " 'required': ['iri', 'name', 'namespace'],\n", - " 'title': 'Annotation',\n", - " 'type': 'object'},\n", - " 'App': {'description': 'App of a KItem.',\n", - " 'properties': {'additionalProperties': {'anyOf': [{'$ref': '#/$defs/AdditionalProperties'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Additional '\n", - " 'properties '\n", - " 'related '\n", - " 'to '\n", - " 'the '\n", - " 'appilcation'},\n", - " 'description': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Description '\n", - " 'of the '\n", - " 'appilcation',\n", - " 'title': 'Description'},\n", - " 'executable': {'description': 'Name of the '\n", - " 'executable '\n", - " 'related to '\n", - " 'the app',\n", - " 'title': 'Executable',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related to '\n", - " 'the KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'kitemAppId': {'anyOf': [{'type': 'integer'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'ID of the '\n", - " 'KItem App',\n", - " 'title': 'Kitemappid'},\n", - " 'tags': {'anyOf': [{'type': 'object'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Tags related to the '\n", - " 'appilcation',\n", - " 'title': 'Tags'},\n", - " 'title': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Title of the '\n", - " 'appilcation',\n", - " 'title': 'Title'}},\n", - " 'required': ['executable'],\n", - " 'title': 'App',\n", - " 'type': 'object'},\n", - " 'Attachment': {'description': 'Attachment uploaded by a certain '\n", - " 'user.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'File name of '\n", - " 'the '\n", - " 'attachment',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name'],\n", - " 'title': 'Attachment',\n", - " 'type': 'object'},\n", - " 'Author': {'description': 'Author of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related '\n", - " 'to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'userId': {'description': 'ID of the DSMS '\n", - " 'User',\n", - " 'format': 'uuid',\n", - " 'title': 'Userid',\n", - " 'type': 'string'}},\n", - " 'required': ['userId'],\n", - " 'title': 'Author',\n", - " 'type': 'object'},\n", - " 'Column': {'description': 'Column of an HDF5 data frame',\n", - " 'properties': {'columnId': {'description': 'Column ID in '\n", - " 'the data '\n", - " 'frame',\n", - " 'title': 'Columnid',\n", - " 'type': 'integer'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related '\n", - " 'to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'column in the '\n", - " 'data series.',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['columnId', 'name'],\n", - " 'title': 'Column',\n", - " 'type': 'object'},\n", - " 'ContactInfo': {'description': 'Contact info',\n", - " 'properties': {'email': {'description': 'EMail of '\n", - " 'the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Email',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'userId': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'User ID '\n", - " 'of the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Userid'}},\n", - " 'required': ['name', 'email'],\n", - " 'title': 'ContactInfo',\n", - " 'type': 'object'},\n", - " 'ExternalLink': {'description': 'External link of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'label': {'description': 'Label of '\n", - " 'the '\n", - " 'external '\n", - " 'link',\n", - " 'title': 'Label',\n", - " 'type': 'string'},\n", - " 'url': {'description': 'URL of the '\n", - " 'external '\n", - " 'link',\n", - " 'format': 'uri',\n", - " 'minLength': 1,\n", - " 'title': 'Url',\n", - " 'type': 'string'}},\n", - " 'required': ['label', 'url'],\n", - " 'title': 'ExternalLink',\n", - " 'type': 'object'},\n", - " 'KItem': {'additionalProperties': False,\n", - " 'description': 'Knowledge Item of the DSMS.',\n", - " 'properties': {'affiliations': {'default': [],\n", - " 'description': 'Affiliations '\n", - " 'related '\n", - " 'to a '\n", - " 'KItem.',\n", - " 'items': {'$ref': '#/$defs/Affiliation'},\n", - " 'title': 'Affiliations',\n", - " 'type': 'array'},\n", - " 'annotations': {'default': [],\n", - " 'description': 'Annotations '\n", - " 'of the '\n", - " 'KItem',\n", - " 'items': {'$ref': '#/$defs/Annotation'},\n", - " 'title': 'Annotations',\n", - " 'type': 'array'},\n", - " 'attachments': {'default': [],\n", - " 'description': 'File '\n", - " 'attachements '\n", - " 'of the '\n", - " 'DSMS',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/Attachment'},\n", - " {'type': 'string'}]},\n", - " 'title': 'Attachments',\n", - " 'type': 'array'},\n", - " 'authors': {'default': [],\n", - " 'description': 'Authorship of '\n", - " 'the KItem.',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/Author'},\n", - " {'type': 'string'}]},\n", - " 'title': 'Authors',\n", - " 'type': 'array'},\n", - " 'avatar_exists': {'anyOf': [{'type': 'boolean'},\n", - " {'type': 'null'}],\n", - " 'default': False,\n", - " 'description': 'Whether '\n", - " 'the '\n", - " 'KItem '\n", - " 'holds an '\n", - " 'avatar '\n", - " 'or not.',\n", - " 'title': 'Avatar Exists'},\n", - " 'contacts': {'default': [],\n", - " 'description': 'Whether the '\n", - " 'KItem holds '\n", - " 'any contact '\n", - " 'information.',\n", - " 'items': {'$ref': '#/$defs/ContactInfo'},\n", - " 'title': 'Contacts',\n", - " 'type': 'array'},\n", - " 'created_at': {'anyOf': [{'type': 'string'},\n", - " {'format': 'date-time',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Time and '\n", - " 'date when '\n", - " 'the KItem '\n", - " 'was '\n", - " 'created.',\n", - " 'title': 'Created At'},\n", - " 'custom_properties': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': {},\n", - " 'description': 'Custom '\n", - " 'properties '\n", - " 'associated '\n", - " 'to '\n", - " 'the '\n", - " 'KItem',\n", - " 'title': 'Custom '\n", - " 'Properties'},\n", - " 'external_links': {'default': [],\n", - " 'description': 'External '\n", - " 'links '\n", - " 'related '\n", - " 'to the '\n", - " 'KItem',\n", - " 'items': {'$ref': '#/$defs/ExternalLink'},\n", - " 'title': 'External '\n", - " 'Links',\n", - " 'type': 'array'},\n", - " 'hdf5': {'anyOf': [{'items': {'$ref': '#/$defs/Column'},\n", - " 'type': 'array'},\n", - " {'additionalProperties': {'anyOf': [{'items': {},\n", - " 'type': 'array'},\n", - " {'type': 'object'}]},\n", - " 'type': 'object'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'HDF5 interface.',\n", - " 'title': 'Hdf5'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'description': 'ID of the KItem',\n", - " 'title': 'Id'},\n", - " 'kitem_apps': {'default': [],\n", - " 'description': 'Apps '\n", - " 'related to '\n", - " 'the KItem.',\n", - " 'items': {'$ref': '#/$defs/App'},\n", - " 'title': 'Kitem Apps',\n", - " 'type': 'array'},\n", - " 'ktype': {'anyOf': [{'$ref': '#/$defs/KType'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KType of the '\n", - " 'KItem'},\n", - " 'ktype_id': {'anyOf': [{'description': 'Create '\n", - " 'a '\n", - " 'collection '\n", - " 'of '\n", - " 'name/value '\n", - " 'pairs.\\n'\n", - " '\\n'\n", - " 'Example '\n", - " 'enumeration:\\n'\n", - " '\\n'\n", - " '>>> '\n", - " 'class '\n", - " 'Color(Enum):\\n'\n", - " '... '\n", - " 'RED '\n", - " '= '\n", - " '1\\n'\n", - " '... '\n", - " 'BLUE '\n", - " '= '\n", - " '2\\n'\n", - " '... '\n", - " 'GREEN '\n", - " '= '\n", - " '3\\n'\n", - " '\\n'\n", - " 'Access '\n", - " 'them '\n", - " 'by:\\n'\n", - " '\\n'\n", - " '- '\n", - " 'attribute '\n", - " 'access:\\n'\n", - " '\\n'\n", - " ' '\n", - " '>>> '\n", - " 'Color.RED\\n'\n", - " ' '\n", - " '\\n'\n", - " '\\n'\n", - " '- '\n", - " 'value '\n", - " 'lookup:\\n'\n", - " '\\n'\n", - " ' '\n", - " '>>> '\n", - " 'Color(1)\\n'\n", - " ' '\n", - " '\\n'\n", - " '\\n'\n", - " '- '\n", - " 'name '\n", - " 'lookup:\\n'\n", - " '\\n'\n", - " ' '\n", - " '>>> '\n", - " \"Color['RED']\\n\"\n", - " ' '\n", - " '\\n'\n", - " '\\n'\n", - " 'Enumerations '\n", - " 'can '\n", - " 'be '\n", - " 'iterated '\n", - " 'over, '\n", - " 'and '\n", - " 'know '\n", - " 'how '\n", - " 'many '\n", - " 'members '\n", - " 'they '\n", - " 'have:\\n'\n", - " '\\n'\n", - " '>>> '\n", - " 'len(Color)\\n'\n", - " '3\\n'\n", - " '\\n'\n", - " '>>> '\n", - " 'list(Color)\\n'\n", - " '[, '\n", - " ', '\n", - " ']\\n'\n", - " '\\n'\n", - " 'Methods '\n", - " 'can '\n", - " 'be '\n", - " 'added '\n", - " 'to '\n", - " 'enumerations, '\n", - " 'and '\n", - " 'members '\n", - " 'can '\n", - " 'have '\n", - " 'their '\n", - " 'own\\n'\n", - " 'attributes '\n", - " '-- '\n", - " 'see '\n", - " 'the '\n", - " 'documentation '\n", - " 'for '\n", - " 'details.',\n", - " 'enum': [],\n", - " 'title': 'Enum'},\n", - " {'type': 'string'}],\n", - " 'description': 'Type ID of '\n", - " 'the KItem',\n", - " 'title': 'Ktype Id'},\n", - " 'linked_kitems': {'default': [],\n", - " 'description': 'KItems '\n", - " 'linked '\n", - " 'to the '\n", - " 'current '\n", - " 'KItem.',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/LinkedKItem'},\n", - " {'$ref': '#/$defs/KItem'}]},\n", - " 'title': 'Linked Kitems',\n", - " 'type': 'array'},\n", - " 'name': {'description': 'Human readable '\n", - " 'name of the '\n", - " 'KContext.dsms',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'slug': {'anyOf': [{'minLength': 4,\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Slug of the '\n", - " 'KContext.dsms',\n", - " 'title': 'Slug'},\n", - " 'summary': {'anyOf': [{'type': 'string'},\n", - " {'$ref': '#/$defs/Summary'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Human readable '\n", - " 'summary text '\n", - " 'of the KItem.',\n", - " 'title': 'Summary'},\n", - " 'updated_at': {'anyOf': [{'type': 'string'},\n", - " {'format': 'date-time',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Time and '\n", - " 'date when '\n", - " 'the KItem '\n", - " 'was '\n", - " 'updated.',\n", - " 'title': 'Updated At'},\n", - " 'user_groups': {'default': [],\n", - " 'description': 'User '\n", - " 'groups '\n", - " 'able to '\n", - " 'access the '\n", - " 'KItem.',\n", - " 'items': {'$ref': '#/$defs/UserGroup'},\n", - " 'title': 'User Groups',\n", - " 'type': 'array'}},\n", - " 'required': ['name', 'ktype_id'],\n", - " 'title': 'KItem',\n", - " 'type': 'object'},\n", - " 'KType': {'description': 'Knowledge type of the knowledge item.',\n", - " 'properties': {'data_schema': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'OpenAPI '\n", - " 'schema of '\n", - " 'the KItem.',\n", - " 'title': 'Data Schema'},\n", - " 'form_data': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Form data of '\n", - " 'the KItem.',\n", - " 'title': 'Form Data'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'string'}],\n", - " 'description': 'ID of the KType.',\n", - " 'title': 'Id'},\n", - " 'name': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Human readable '\n", - " 'name of the '\n", - " 'KType.',\n", - " 'title': 'Name'}},\n", - " 'required': ['id'],\n", - " 'title': 'KType',\n", - " 'type': 'object'},\n", - " 'LinkedKItem': {'description': 'Data model of a linked KItem',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'ID of the '\n", - " 'KItem to be '\n", - " 'linked',\n", - " 'title': 'Id'},\n", - " 'sourceId': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Source '\n", - " 'ID of '\n", - " 'the '\n", - " 'KItem',\n", - " 'title': 'Sourceid'}},\n", - " 'title': 'LinkedKItem',\n", - " 'type': 'object'},\n", - " 'Summary': {'additionalProperties': False,\n", - " 'description': 'Model for the custom properties of the '\n", - " 'KItem',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'title': 'Id'},\n", - " 'kitem': {'anyOf': [{}, {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem related '\n", - " 'to the summary',\n", - " 'title': 'Kitem'},\n", - " 'text': {'description': 'Summary text of '\n", - " 'the KItem',\n", - " 'title': 'Text',\n", - " 'type': 'string'}},\n", - " 'required': ['text'],\n", - " 'title': 'Summary',\n", - " 'type': 'object'},\n", - " 'UserGroup': {'description': 'Users groups related to a KItem.',\n", - " 'properties': {'groupId': {'description': 'ID of the '\n", - " 'user group',\n", - " 'title': 'Groupid',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'user group',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name', 'groupId'],\n", - " 'title': 'UserGroup',\n", - " 'type': 'object'}},\n", - " 'allOf': [{'$ref': '#/$defs/KItem'}]}\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Anaconda3\\envs\\sdk\\Lib\\site-packages\\pydantic\\json_schema.py:2099: PydanticJsonSchemaWarning: Default value is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n", - " warnings.warn(message, PydanticJsonSchemaWarning)\n" - ] - } - ], + "outputs": [], "source": [ "pprint(KItem.model_json_schema())" ] @@ -734,22 +123,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "KTypes.Organization\n", - "KTypes.Expert\n", - "KTypes.App\n", - "KTypes.DatasetCatalog\n", - "KTypes.TestSeries\n", - "KTypes.TestMatthias\n" - ] - } - ], + "outputs": [], "source": [ "for ktype in dsms.ktypes:\n", " print(ktype)" @@ -771,62 +147,12 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "KItem(\n", - "\n", - "\tname = foo123, \n", - "\n", - "\tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", - "\n", - "\tktype_id = KTypes.DatasetCatalog, \n", - "\n", - "\tslug = foo123, \n", - "\n", - "\tannotations = [], \n", - "\n", - "\tattachments = [], \n", - "\n", - "\tlinked_kitems = [], \n", - "\n", - "\taffiliations = [], \n", - "\n", - "\tauthors = [], \n", - "\n", - "\tavatar_exists = False, \n", - "\n", - "\tcontacts = [], \n", - "\n", - "\tcreated_at = None, \n", - "\n", - "\tupdated_at = None, \n", - "\n", - "\texternal_links = [], \n", - "\n", - "\tkitem_apps = [], \n", - "\n", - "\tsummary = None, \n", - "\n", - "\tuser_groups = [], \n", - "\n", - "\tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", - "\n", - "\thdf5 = None\n", - ")" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item = KItem(\n", - " name=\"foo123\",\n", + " name=\"foo12345\",\n", " ktype_id=dsms.ktypes.DatasetCatalog,\n", " custom_properties={\"foo\": \"bar\"},\n", ")\n", @@ -843,7 +169,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -859,61 +185,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "KItem(\n", - "\n", - "\tname = foo123, \n", - "\n", - "\tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", - "\n", - "\tktype_id = dataset-catalog, \n", - "\n", - "\tslug = foo123, \n", - "\n", - "\tannotations = [], \n", - "\n", - "\tattachments = [], \n", - "\n", - "\tlinked_kitems = [], \n", - "\n", - "\taffiliations = [], \n", - "\n", - "\tauthors = [\n", - "\t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - "\t], \n", - "\n", - "\tavatar_exists = False, \n", - "\n", - "\tcontacts = [], \n", - "\n", - "\tcreated_at = 2024-03-06 10:12:39.377020, \n", - "\n", - "\tupdated_at = 2024-03-06 10:12:39.377020, \n", - "\n", - "\texternal_links = [], \n", - "\n", - "\tkitem_apps = [], \n", - "\n", - "\tsummary = None, \n", - "\n", - "\tuser_groups = [], \n", - "\n", - "\tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", - "\n", - "\thdf5 = None\n", - ")" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item" ] @@ -927,61 +201,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = foo123, \n", - " \n", - " \tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = foo123, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-06 10:12:39.377020, \n", - " \n", - " \tupdated_at = 2024-03-06 10:12:39.377020, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.kitems" ] @@ -1007,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1041,7 +263,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1064,58 +286,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\t\t\t Downloaded file: Attachment(name=README.md)\n", - "|------------------------------------Beginning of file------------------------------------|\n", - "# DSMS-SDK\n", - "Python SDK core-package for interacting with the Dataspace Management System (DSMS)\n", - "\n", - "\n", - "## Authors\n", - "\n", - "[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "## License\n", - "\n", - "This project is licensed under the BSD 3-Clause. See the LICENSE file for more information.\n", - "\n", - "## Usage\n", - "\n", - "The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities:\n", - "\n", - "- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType)\n", - " - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test\n", - " - Administrating authorship, contact information and supplementary information upon making changes or adding KItems\n", - " - Semantic annotation of KItems\n", - "- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface\n", - "- Linking KItems to other KItems\n", - "- Linking Apps to KItems, triggererd, for example, during a file upload\n", - "- Performing simple file upload and download using attachments to KItems\n", - "- Export of a knowledge (sub) graph as common serializations (.ttl, .json)\n", - "\n", - "For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items.\n", - "\n", - "\n", - "## Disclaimer\n", - "\n", - "Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM.\n", - "\n", - "Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de)\n", - "\n", - "|---------------------------------------End of file---------------------------------------|\n" - ] - } - ], + "outputs": [], "source": [ "for file in item.attachments:\n", " download = file.download()\n", @@ -1148,20 +321,9 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "UserGroup(name=foogroup, group_id=123)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item.attachments.pop(0)\n", "item.annotations.pop(0)\n", @@ -1179,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1195,61 +357,9 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "KItem(\n", - "\n", - "\tname = foobar, \n", - "\n", - "\tid = 23fcb340-dec7-483b-9236-b30d8f8ea01c, \n", - "\n", - "\tktype_id = dataset-catalog, \n", - "\n", - "\tslug = foo123, \n", - "\n", - "\tannotations = [], \n", - "\n", - "\tattachments = [], \n", - "\n", - "\tlinked_kitems = [], \n", - "\n", - "\taffiliations = [], \n", - "\n", - "\tauthors = [\n", - "\t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - "\t], \n", - "\n", - "\tavatar_exists = False, \n", - "\n", - "\tcontacts = [], \n", - "\n", - "\tcreated_at = 2024-03-06 10:12:39.377020, \n", - "\n", - "\tupdated_at = 2024-03-06 10:13:06.308318, \n", - "\n", - "\texternal_links = [], \n", - "\n", - "\tkitem_apps = [], \n", - "\n", - "\tsummary = None, \n", - "\n", - "\tuser_groups = [], \n", - "\n", - "\tcustom_properties = CustomProperties(content={'foobar': 'foobar'}), \n", - "\n", - "\thdf5 = None\n", - ")" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item" ] @@ -1263,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1279,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1295,7 +405,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1311,20 +421,9 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.kitems" ] @@ -1351,7 +450,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1400,540 +499,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = excel_component_test, \n", - " \n", - " \tid = 87eed190-3979-4c5f-bae5-2dbad25f2f12, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_component_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:45:15.506823, \n", - " \n", - " \tupdated_at = 2024-03-05 16:45:15.506823, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_ebsd, \n", - " \n", - " \tid = c113be0f-77cf-447e-b14f-a84c893cec23, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_ebsd, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang), \n", - " \t\tAttachment(name=DP800_111_WR00_500x_150x150um.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR00_196_merged_cleaned.ang), \n", - " \t\tAttachment(name=DP800_112_WR90_500x_150x150.ang), \n", - " \t\tAttachment(name=dx56_mitte_quer_850x850_step1-5.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR90_197_merged_cleaned.ang), \n", - " \t\tAttachment(name=dx56_mitte_laengs_850x850_step1-5.ang)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:45:48.101341, \n", - " \n", - " \tupdated_at = 2024-03-05 16:45:48.101341, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_bulgetest, \n", - " \n", - " \tid = e64c512a-c2e5-428d-bc85-5d0d9c2d68c7, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_bulgetest, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DX56_C_BLG_WR90_42.TXT), \n", - " \t\tAttachment(name=AA6014_O_BLG_WR90_241.TXT), \n", - " \t\tAttachment(name=DP800_H_BLG_WR90_140.TXT), \n", - " \t\tAttachment(name=DX56_D_BLG_WR90_56.TXT), \n", - " \t\tAttachment(name=AFZ1-BU-S8.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S1.TXT), \n", - " \t\tAttachment(name=AA6014_N_BLG_WR90_218.TXT), \n", - " \t\tAttachment(name=DP800_G_BLG_WR90_126.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S7.TXT), \n", - " \t\tAttachment(name=AA6014_M_BLG_WR90_195.TXT), \n", - " \t\tAttachment(name=DX56_B_BLG_WR90_28.TXT), \n", - " \t\tAttachment(name=DP800_F_BLG_WR90_108.TXT)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:46:41.767008, \n", - " \n", - " \tupdated_at = 2024-03-05 16:46:41.767008, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = excel_shear_tensile_test, \n", - " \n", - " \tid = e9c4bb04-9482-458d-9d12-c2ad8f214930, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_shear_tensile_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:47:27.228060, \n", - " \n", - " \tupdated_at = 2024-03-05 16:47:27.228060, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = excel_tensile_test, \n", - " \n", - " \tid = e1f1b94c-5bd6-44c7-9de4-1d01650ac7b8, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_tensile_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:48:01.936787, \n", - " \n", - " \tupdated_at = 2024-03-05 16:48:01.936787, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=105, executable=excel_tensile_test/excel_tensile_test.ipynb, title=excel_tensile_test, description=excel_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.xls', '.xlsm'])\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_tensile_test, \n", - " \n", - " \tid = 90207798-d6b2-47fb-b208-f7b8f771de0d, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_tensile_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DX56_D_FZ2_WR00_43.TXT)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 17:08:20.345033, \n", - " \n", - " \tupdated_at = 2024-03-05 17:08:20.345033, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=107, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT']), \n", - " \t\tApp(kitem_app_id=108, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = [\n", - " \t\tColumn(column_id=0, name=Prüfzeit), \n", - " \t\tColumn(column_id=1, name=Standardkraft), \n", - " \t\tColumn(column_id=2, name=Traversenweg absolut), \n", - " \t\tColumn(column_id=3, name=Standardweg), \n", - " \t\tColumn(column_id=4, name=Breitenänderung), \n", - " \t\tColumn(column_id=5, name=Dehnung)\n", - " \t]\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_tensile_test_f2, \n", - " \n", - " \tid = c9203684-f1ce-4d23-92f9-478a002b7440, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_tensile_test_f2, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DP800_F_FZ2_WR15_97.TXT)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 17:08:27.579333, \n", - " \n", - " \tupdated_at = 2024-03-05 17:08:27.579333, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=109, executable=csv_tensile_test_f2/csv_tensile_test_f2.ipynb, title=csv_tensile_test_f2, description=csv_tensile_test_f2, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = material constants, \n", - " \n", - " \tid = ea0bb87b-3a26-48be-84e1-bec9b99a1204, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = material_constants, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/MaterialCard, name=MaterialCard, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-27 05:31:47.433035, \n", - " \n", - " \tupdated_at = 2024-02-27 05:31:47.433035, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={'material constants': {'C11': 226000.0, 'C12': 140000.0, 'C44': 116000.0, 'shear_rate_ref': 0.001, 'rate_sens': 0.0125, 'tau0': 62.1930648, 'tau1': 48.1881715, 'theta0': 356.842243, 'theta1': 37.4360252, 'q1': 1.4, 'q2': 1.4}}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = orientations, \n", - " \n", - " \tid = 5386bf18-533a-4976-8481-d54638f07a91, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = orientations, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/GrainOrientation, name=GrainOrientation, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=5386bf18-533a-4976-8481-d54638f07a91), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=5386bf18-533a-4976-8481-d54638f07a91)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-27 05:31:47.498209, \n", - " \n", - " \tupdated_at = 2024-02-27 05:31:47.498209, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = rve_1000_grains_geofile, \n", - " \n", - " \tid = 13e57827-c5fe-413e-90dd-bb95ec5c1d3a, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = rve_1000_grains_geofile, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/Geometry, name=Geometry, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=mesh_40x40x40_grains_1000_aspect_ratio_1_6.geo)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=13e57827-c5fe-413e-90dd-bb95ec5c1d3a)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-27 05:31:47.588776, \n", - " \n", - " \tupdated_at = 2024-02-27 05:31:47.588776, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = 8377f120-d558-4cc2-93d4-582bb01fac11, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = foo1, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=5dacf60c-6a62-466a-a85e-9f3ce422cefa, source_id=8377f120-d558-4cc2-93d4-582bb01fac11)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-06 10:13:29.213578, \n", - " \n", - " \tupdated_at = 2024-03-06 10:13:29.213578, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" ] @@ -1947,538 +515,9 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = excel_component_test, \n", - " \n", - " \tid = 87eed190-3979-4c5f-bae5-2dbad25f2f12, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_component_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:45:15.506823, \n", - " \n", - " \tupdated_at = 2024-03-05 16:45:15.506823, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_ebsd, \n", - " \n", - " \tid = c113be0f-77cf-447e-b14f-a84c893cec23, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_ebsd, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang), \n", - " \t\tAttachment(name=DP800_111_WR00_500x_150x150um.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR00_196_merged_cleaned.ang), \n", - " \t\tAttachment(name=DP800_112_WR90_500x_150x150.ang), \n", - " \t\tAttachment(name=dx56_mitte_quer_850x850_step1-5.ang), \n", - " \t\tAttachment(name=AA6014_M_REM_WR90_197_merged_cleaned.ang), \n", - " \t\tAttachment(name=dx56_mitte_laengs_850x850_step1-5.ang)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:45:48.101341, \n", - " \n", - " \tupdated_at = 2024-03-05 16:45:48.101341, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_bulgetest, \n", - " \n", - " \tid = e64c512a-c2e5-428d-bc85-5d0d9c2d68c7, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_bulgetest, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DX56_C_BLG_WR90_42.TXT), \n", - " \t\tAttachment(name=AA6014_O_BLG_WR90_241.TXT), \n", - " \t\tAttachment(name=DP800_H_BLG_WR90_140.TXT), \n", - " \t\tAttachment(name=DX56_D_BLG_WR90_56.TXT), \n", - " \t\tAttachment(name=AFZ1-BU-S8.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S1.TXT), \n", - " \t\tAttachment(name=AA6014_N_BLG_WR90_218.TXT), \n", - " \t\tAttachment(name=DP800_G_BLG_WR90_126.TXT), \n", - " \t\tAttachment(name=AFZ-BU-S7.TXT), \n", - " \t\tAttachment(name=AA6014_M_BLG_WR90_195.TXT), \n", - " \t\tAttachment(name=DX56_B_BLG_WR90_28.TXT), \n", - " \t\tAttachment(name=DP800_F_BLG_WR90_108.TXT)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:46:41.767008, \n", - " \n", - " \tupdated_at = 2024-03-05 16:46:41.767008, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = excel_shear_tensile_test, \n", - " \n", - " \tid = e9c4bb04-9482-458d-9d12-c2ad8f214930, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_shear_tensile_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:47:27.228060, \n", - " \n", - " \tupdated_at = 2024-03-05 16:47:27.228060, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = excel_tensile_test, \n", - " \n", - " \tid = e1f1b94c-5bd6-44c7-9de4-1d01650ac7b8, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = excel_tensile_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 16:48:01.936787, \n", - " \n", - " \tupdated_at = 2024-03-05 16:48:01.936787, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=105, executable=excel_tensile_test/excel_tensile_test.ipynb, title=excel_tensile_test, description=excel_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.xls', '.xlsm'])\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_tensile_test, \n", - " \n", - " \tid = 90207798-d6b2-47fb-b208-f7b8f771de0d, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_tensile_test, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DX56_D_FZ2_WR00_43.TXT)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 17:08:20.345033, \n", - " \n", - " \tupdated_at = 2024-03-05 17:08:20.345033, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=107, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT']), \n", - " \t\tApp(kitem_app_id=108, executable=csv_tensile_test/csv_tensile_test.ipynb, title=csv_tensile_test, description=csv_tensile_test, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = [\n", - " \t\tColumn(column_id=0, name=Prüfzeit), \n", - " \t\tColumn(column_id=1, name=Standardkraft), \n", - " \t\tColumn(column_id=2, name=Traversenweg absolut), \n", - " \t\tColumn(column_id=3, name=Standardweg), \n", - " \t\tColumn(column_id=4, name=Breitenänderung), \n", - " \t\tColumn(column_id=5, name=Dehnung)\n", - " \t]\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = csv_tensile_test_f2, \n", - " \n", - " \tid = c9203684-f1ce-4d23-92f9-478a002b7440, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csv_tensile_test_f2, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=DP800_F_FZ2_WR15_97.TXT)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=83778ed4-2d3d-4fae-a1f0-35f59bb89799)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-05 17:08:27.579333, \n", - " \n", - " \tupdated_at = 2024-03-05 17:08:27.579333, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\tApp(kitem_app_id=109, executable=csv_tensile_test_f2/csv_tensile_test_f2.ipynb, title=csv_tensile_test_f2, description=csv_tensile_test_f2, tags=None, additional_properties=triggerUponUpload=True triggerUponUploadFileExtensions=['.TXT'])\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = material constants, \n", - " \n", - " \tid = ea0bb87b-3a26-48be-84e1-bec9b99a1204, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = material_constants, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/MaterialCard, name=MaterialCard, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=ea0bb87b-3a26-48be-84e1-bec9b99a1204)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-27 05:31:47.433035, \n", - " \n", - " \tupdated_at = 2024-02-27 05:31:47.433035, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={'material constants': {'C11': 226000.0, 'C12': 140000.0, 'C44': 116000.0, 'shear_rate_ref': 0.001, 'rate_sens': 0.0125, 'tau0': 62.1930648, 'tau1': 48.1881715, 'theta0': 356.842243, 'theta1': 37.4360252, 'q1': 1.4, 'q2': 1.4}}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = orientations, \n", - " \n", - " \tid = 5386bf18-533a-4976-8481-d54638f07a91, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = orientations, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/GrainOrientation, name=GrainOrientation, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=11533_FS_Scan1_830x830_100x.ang)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=72bc54cf-8138-4ac3-8586-14afe5bc6b7f, source_id=5386bf18-533a-4976-8481-d54638f07a91), \n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=5386bf18-533a-4976-8481-d54638f07a91)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-27 05:31:47.498209, \n", - " \n", - " \tupdated_at = 2024-02-27 05:31:47.498209, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = rve_1000_grains_geofile, \n", - " \n", - " \tid = 13e57827-c5fe-413e-90dd-bb95ec5c1d3a, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = rve_1000_grains_geofile, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=https://w3id.org/steel/ProcessOntology/Geometry, name=Geometry, namespace=https://w3id.org/steel/ProcessOntology)\n", - " \t], \n", - " \n", - " \tattachments = [\n", - " \t\tAttachment(name=mesh_40x40x40_grains_1000_aspect_ratio_1_6.geo)\n", - " \t], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=253d69c1-606c-4721-832e-1caed961e8ef, source_id=13e57827-c5fe-413e-90dd-bb95ec5c1d3a)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=8f40087b-16d6-49ba-a8ac-2bd70b8a2308)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-27 05:31:47.588776, \n", - " \n", - " \tupdated_at = 2024-02-27 05:31:47.588776, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = foo 3, \n", - " \n", - " \tid = 17ec4319-7643-40e8-b3d2-a16fbafd9571, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tslug = foo3, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-06 10:13:30.805087, \n", - " \n", - " \tupdated_at = 2024-03-06 10:13:30.805087, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" ] @@ -2492,63 +531,9 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = 8377f120-d558-4cc2-93d4-582bb01fac11, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = foo1, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=5dacf60c-6a62-466a-a85e-9f3ce422cefa, source_id=8377f120-d558-4cc2-93d4-582bb01fac11)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-06 10:13:29.213578, \n", - " \n", - " \tupdated_at = 2024-03-06 10:13:29.213578, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" ] @@ -2562,109 +547,9 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = foo 2, \n", - " \n", - " \tid = 5dacf60c-6a62-466a-a85e-9f3ce422cefa, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tslug = foo2, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=www.example.org/foo, name=foo, namespace=www.example.org)\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\tLinkedKItem(id=8377f120-d558-4cc2-93d4-582bb01fac11, source_id=5dacf60c-6a62-466a-a85e-9f3ce422cefa)\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-06 10:13:29.996514, \n", - " \n", - " \tupdated_at = 2024-03-06 10:13:29.996514, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " ),\n", - " KItem(\n", - " \n", - " \tname = foo 4, \n", - " \n", - " \tid = 6e0574de-e358-4305-9148-244132506044, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tslug = foo4, \n", - " \n", - " \tannotations = [\n", - " \t\tAnnotation(iri=www.example.org/bar, name=bar, namespace=https://www.example.org)\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\tAuthor(user_id=4440f8c8-f443-4114-a7a7-d8e2a6b5d776)\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-03-06 10:13:31.643039, \n", - " \n", - " \tupdated_at = 2024-03-06 10:13:31.643039, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = CustomProperties(content={}), \n", - " \n", - " \thdf5 = None\n", - " )]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.search(\n", " ktypes=[dsms.ktypes.Organization], annotations=[\"www.example.org/foo\"]\n", @@ -2680,20 +565,9 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "del dsms[item]\n", "del dsms[item2]\n", @@ -2721,25 +595,9 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[App(filename='bulk_clone_pipelines.ipynb', basename='bulk_clone_pipelines.ipynb', folder=''),\n", - " App(filename='csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='csv_tensile_test'),\n", - " App(filename='csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='csv_tensile_test_f2'),\n", - " App(filename='excel_nakajima_test/excel_nakajima_test.ipynb', basename='excel_nakajima_test.ipynb', folder='excel_nakajima_test'),\n", - " App(filename='excel_shear_test/excel_shear_tensile_test.ipynb', basename='excel_shear_tensile_test.ipynb', folder='excel_shear_test'),\n", - " App(filename='excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='excel_tensile_test')]" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.apps" ] @@ -2760,37 +618,9 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Column-wise:\n", - "column: a ,\n", - " data: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0]\n", - "column: b ,\n", - " data: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0, 100.0]\n", - "\n", - "As data frame:\n", - " a b\n", - "0 0.0 1.0\n", - "1 1.0 2.0\n", - "2 2.0 3.0\n", - "3 3.0 4.0\n", - "4 4.0 5.0\n", - ".. ... ...\n", - "95 95.0 96.0\n", - "96 96.0 97.0\n", - "97 97.0 98.0\n", - "98 98.0 99.0\n", - "99 99.0 100.0\n", - "\n", - "[100 rows x 2 columns]\n" - ] - } - ], + "outputs": [], "source": [ "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", "\n", @@ -2814,7 +644,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2847,7 +677,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.9.19" } }, "nbformat": 4, diff --git a/requirements_PCL590_thesis.txt b/requirements_PCL590_thesis.txt new file mode 100644 index 0000000..d1a8f01 --- /dev/null +++ b/requirements_PCL590_thesis.txt @@ -0,0 +1,265 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: win-64 +@EXPLICIT +https://repo.anaconda.com/pkgs/main/win-64/blas-1.0-mkl.conda +https://repo.anaconda.com/pkgs/main/win-64/ca-certificates-2024.3.11-haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_1.conda +https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.1.0-h57928b3_964.conda +https://conda.anaconda.org/conda-forge/win-64/libasprintf-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/msys2-conda-epoch-20160418-1.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/pandoc-3.1.13-h57928b3_0.conda +https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 +https://repo.anaconda.com/pkgs/main/noarch/tzdata-2024a-h04d1e81_0.conda +https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 +https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/expat-2.6.2-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/libasprintf-devel-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/win-64/m2w64-gmp-6.1.0-2.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/m2w64-libwinpthread-git-5.0.0.4634.697f757-2.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda +https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/vc-14.2-h21ff451_1.conda +https://conda.anaconda.org/conda-forge/win-64/aom-3.8.2-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda +https://conda.anaconda.org/conda-forge/win-64/dav1d-1.2.1-hcfcfb64_0.conda +https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.3.0-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/freeglut-3.2.2-h63175ca_2.conda +https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.13-h63175ca_1003.conda +https://conda.anaconda.org/conda-forge/win-64/icu-73.2-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/khronos-opencl-icd-loader-2023.04.17-h64bf75a_0.conda +https://conda.anaconda.org/conda-forge/win-64/lerc-4.0.0-h63175ca_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/libabseil-20240116.1-cxx17_h63175ca_2.conda +https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.3-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.20-hcfcfb64_0.conda +https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/libiconv-1.17-hcfcfb64_2.conda +https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.0.0-hcfcfb64_1.conda +https://conda.anaconda.org/conda-forge/win-64/libopus-1.3.1-h8ffe710_1.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.18-h8d14728_1.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.2-hcfcfb64_0.conda +https://repo.anaconda.com/pkgs/main/win-64/libuv-1.44.2-h2bbff1b_0.conda +https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.3.2-hcfcfb64_0.conda +https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda +https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/mpfr-4.0.2-h62dcd97_1.conda +https://repo.anaconda.com/pkgs/main/win-64/mpir-3.0.0-hec2e145_1.conda +https://conda.anaconda.org/conda-forge/win-64/openh264-2.4.1-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda +https://conda.anaconda.org/conda-forge/win-64/pixman-0.43.4-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/pthreads-win32-2.9.1-hfa6e2cd_3.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/pugixml-1.14-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/snappy-1.1.10-hfb803bf_0.conda +https://repo.anaconda.com/pkgs/main/win-64/sqlite-3.41.2-h2bbff1b_0.conda +https://conda.anaconda.org/conda-forge/win-64/svt-av1-2.0.0-h63175ca_0.conda +https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda +https://conda.anaconda.org/conda-forge/win-64/x264-1!164.3095-h8ffe710_2.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/x265-3.5-h2d74725_3.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/yaml-0.2.5-he774522_0.conda +https://conda.anaconda.org/conda-forge/win-64/imath-3.1.11-h12be248_0.conda +https://conda.anaconda.org/conda-forge/win-64/jasper-4.2.3-h28f2b1a_0.conda +https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.2-heb0366b_0.conda +https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.43-h19919ed_0.conda +https://conda.anaconda.org/conda-forge/win-64/libprotobuf-4.25.3-h503648d_0.conda +https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.0-h7dfc565_0.conda +https://conda.anaconda.org/conda-forge/win-64/libxml2-2.12.6-hc3477c8_1.conda +https://repo.anaconda.com/pkgs/main/win-64/mpc-1.1.0-h7edee0f_1.conda +https://conda.anaconda.org/conda-forge/win-64/pcre2-10.43-h17e33f8_0.conda +https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hcd874cb_1001.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/python-3.9.19-h1aa4202_0.conda +https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.11-hcd874cb_0.conda +https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.3-hcd874cb_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h63175ca_1.conda +https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.13-hcfcfb64_5.conda +https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.5-h12be248_0.conda +https://conda.anaconda.org/conda-forge/noarch/attrs-23.2.0-pyh71513ae_0.conda +https://repo.anaconda.com/pkgs/main/win-64/brotli-python-1.0.9-py39hd77b12b_7.conda +https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/certifi-2024.2.2-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_2.conda +https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/filelock-3.13.1-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda +https://conda.anaconda.org/conda-forge/win-64/gettext-tools-0.22.5-h7d00a51_2.conda +https://repo.anaconda.com/pkgs/main/win-64/gmpy2-2.1.2-py39h7f96b67_0.conda +https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/idna-3.4-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/json5-0.9.24-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.10-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/libclang13-18.1.2-default_hf64faad_1.conda +https://conda.anaconda.org/conda-forge/win-64/libcurl-8.7.1-hd5e4a3a_0.conda +https://conda.anaconda.org/conda-forge/win-64/libgettextpo-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/win-64/libglib-2.80.0-h39d0aa6_3.conda +https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.9.3-default_haede6df_1009.conda +https://conda.anaconda.org/conda-forge/win-64/libintl-devel-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/win-64/libtiff-4.6.0-hddb2be6_3.conda +https://conda.anaconda.org/conda-forge/win-64/libxcb-1.15-hcd874cb_0.conda +https://repo.anaconda.com/pkgs/main/win-64/markupsafe-2.1.3-py39h2bbff1b_0.conda +https://conda.anaconda.org/conda-forge/noarch/mistune-3.0.2-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/mpmath-1.3.0-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/networkx-3.1-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/win-64/openexr-3.2.2-h72640d8_1.conda +https://conda.anaconda.org/conda-forge/noarch/packaging-24.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-py_1003.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_1.conda +https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.2.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.20.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.2-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/pygments-2.17.2-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.19.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2024.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/python_abi-3.9-2_cp39.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pyyaml-6.0.1-py39h2bbff1b_0.conda +https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/setuptools-68.2.2-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.5-pyhd8ed1ab_1.conda +https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.2-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20240316-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/typing_extensions-4.9.0-py39haa95532_1.conda +https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/webcolors-1.13-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_2.conda +https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.7.0-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/wheel-0.41.2-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.10-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/win_inet_pton-1.1.0-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/zipp-3.17.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/anyio-4.3.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/asttokens-2.4.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.4-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/babel-2.14.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda +https://conda.anaconda.org/conda-forge/noarch/bleach-6.1.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/cffi-1.16.0-py39ha55989b_0.conda +https://conda.anaconda.org/conda-forge/noarch/comm-0.2.2-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.1-py39h99910a6_0.conda +https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.14.2-hbde0cde_0.conda +https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.3-nompi_h73e8ff5_100.conda +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-7.1.0-pyha770c72_0.conda +https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.1-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/jinja2-3.1.3-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/win-64/jsonpointer-2.4-py39hcbf5309_3.conda +https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_1.conda +https://conda.anaconda.org/conda-forge/win-64/lcms2-2.16-h67d730c_0.conda +https://conda.anaconda.org/conda-forge/win-64/libgettextpo-devel-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.6-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.2-h3d672ee_0.conda +https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pip-23.3.1-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.42-pyha770c72_0.conda +https://conda.anaconda.org/conda-forge/win-64/psutil-5.9.8-py39ha55989b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/pysocks-1.7.1-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/pywin32-306-py39h99910a6_2.conda +https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.13-py39h99910a6_0.conda +https://conda.anaconda.org/conda-forge/win-64/pyzmq-25.1.2-py39h7eaf5a6_0.conda +https://conda.anaconda.org/conda-forge/noarch/qtpy-2.4.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.18.0-py39hf21820d_0.conda +https://repo.anaconda.com/pkgs/main/win-64/sympy-1.12-py39haa95532_0.conda +https://conda.anaconda.org/conda-forge/win-64/tbb-2021.11.0-h91493d7_1.conda +https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.2.1-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/tornado-6.4-py39ha55989b_0.conda +https://repo.anaconda.com/pkgs/main/win-64/typing-extensions-4.9.0-py39haa95532_1.conda +https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-21.2.0-py39ha55989b_4.conda +https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.0-h1fef639_0.conda +https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/win-64/gettext-0.22.5-h5728263_2.conda +https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.5-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-7.1.0-hd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/jupyter_core-5.7.2-py39hcbf5309_0.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-2024.0.0-hc2557fa_4.conda +https://conda.anaconda.org/conda-forge/win-64/mkl-2024.1.0-h66d3029_692.conda +https://conda.anaconda.org/conda-forge/win-64/pillow-10.3.0-py39h9ee4981_0.conda +https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.42-hd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/referencing-0.34.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.2-pyh08f2357_0.conda +https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.2-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda +https://repo.anaconda.com/pkgs/main/win-64/urllib3-2.1.0-py39haa95532_1.conda +https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-23.1.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/harfbuzz-8.3.0-h7ab893a_0.conda +https://conda.anaconda.org/conda-forge/noarch/httpx-0.27.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/ipython-8.18.1-pyh7428d3b_3.conda +https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_0.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2023.12.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-22_win64_mkl.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-auto-batch-plugin-2024.0.0-h002f227_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-auto-plugin-2024.0.0-h002f227_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-hetero-plugin-2024.0.0-h7e3b17c_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-intel-cpu-plugin-2024.0.0-hc2557fa_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-intel-gpu-plugin-2024.0.0-hc2557fa_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-ir-frontend-2024.0.0-h7e3b17c_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-onnx-frontend-2024.0.0-h55b4db4_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-paddle-frontend-2024.0.0-h55b4db4_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-pytorch-frontend-2024.0.0-h63175ca_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-tensorflow-frontend-2024.0.0-ha362bc9_4.conda +https://conda.anaconda.org/conda-forge/win-64/libopenvino-tensorflow-lite-frontend-2024.0.0-h63175ca_4.conda +https://conda.anaconda.org/pytorch/win-64/pytorch-2.2.2-py3.9_cpu_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/win-64/requests-2.31.0-py39haa95532_1.conda +https://conda.anaconda.org/conda-forge/win-64/ffmpeg-6.1.1-gpl_h66c0b5b_107.conda +https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.29.3-pyha63f2e9_0.conda +https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.2-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.21.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-22_win64_mkl.conda +https://conda.anaconda.org/conda-forge/win-64/liblapack-3.9.0-22_win64_mkl.conda +https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.6.3-h5dee92f_0.conda +https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.21.1-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter_console-6.6.3-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.9.0-22_win64_mkl.conda +https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/numpy-1.26.4-py39hddb5d58_0.conda +https://conda.anaconda.org/conda-forge/noarch/qtconsole-base-5.5.1-pyha770c72_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.10.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/libopencv-4.9.0-qt6_py39h45fa9c4_612.conda +https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/pandas-2.2.1-py39h32e6231_0.conda +https://conda.anaconda.org/pytorch/win-64/torchaudio-2.2.2-py39_cpu.tar.bz2 +https://conda.anaconda.org/pytorch/win-64/torchvision-0.17.2-py39_cpu.tar.bz2 +https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.3-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/py-opencv-4.9.0-qt6_py39h50579d7_612.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.13.0-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.16.3-hd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/win-64/opencv-4.9.0-qt6_py39heec2197_612.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.2.4-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.25.4-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.16.3-hd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.1.5-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/notebook-7.1.2-pyhd8ed1ab_0.conda +https://conda.anaconda.org/conda-forge/noarch/jupyter-1.0.0-pyhd8ed1ab_10.conda From f4b83b05a9a17cd022402ebd185a7c185b7639f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 22 Apr 2024 21:47:37 +0200 Subject: [PATCH 042/114] add cli, autocomplete for units froms iri and displaying of units --- .pylintrc | 1 + dsms/core/configuration.py | 14 +++++++ dsms/knowledge/cli.py | 42 +++++++++++++++++++ .../properties/custom_datatype/numerical.py | 28 ++++++++++++- dsms/knowledge/properties/hdf5.py | 26 +++++++++++- dsms/knowledge/semantics/queries/base.py | 17 ++++++-- dsms/knowledge/semantics/units/base.py | 2 + dsms/knowledge/semantics/units/conversion.py | 23 ++++++++++ dsms/knowledge/semantics/units/sparql.py | 18 ++++++++ dsms/knowledge/semantics/units/utils.py | 11 ++++- dsms/knowledge/utils.py | 2 +- setup.cfg | 4 ++ 12 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 dsms/knowledge/cli.py diff --git a/.pylintrc b/.pylintrc index 7bef9d0..070db46 100644 --- a/.pylintrc +++ b/.pylintrc @@ -76,6 +76,7 @@ disable= no-name-in-module, dangerous-default-value, too-many-public-methods, too-many-locals, + no-value-for-parameter, diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 1d7b23f..36eb8b4 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -69,6 +69,20 @@ class Configuration(BaseSettings): description="Datetime format used in the DSMS instance.", ) + display_units: bool = Field( + False, + description="""Whether the custom properties or the hdf5 columns shall + directly reveal their unit when printed. WARNING: This might lead to performance issues.""", + ) + + autocomplete_units: bool = Field( + True, + description="""When a unit is fetched but does not hold a symbol + next to its URI, it shall be fetched from the respective ontology + (which is general side effect from the `units_sparq_object`.) + WARNING: This might lead to performance issues.""", + ) + kitem_repo: str = Field( DEFAULT_REPO, description="Repository of the triplestore for KItems in the DSMS", diff --git a/dsms/knowledge/cli.py b/dsms/knowledge/cli.py new file mode 100644 index 0000000..2889616 --- /dev/null +++ b/dsms/knowledge/cli.py @@ -0,0 +1,42 @@ +"""Command line interface for kitem inspection""" + + +import click +from pydantic import BaseModel + +from dsms import DSMS +from dsms.knowledge.properties import HDF5Container + + +@click.command() +@click.argument("kitem-id") +@click.option("-p", "--kitem-property", help="Name of the kitem property") +@click.option("-e", "--env", default=".env", help="Env file to load for the ") +@click.option( + "-k", + "--key", + default=None, + help="key of the subproperty of the KItem Property. E.g. a list index or a column name", +) +def lookup_kitem(kitem_id, kitem_property, env, key): + """Simple CLI for looking up KItems from the DSMS""" + dsms = DSMS(env=env) + item = dsms[kitem_id] + if kitem_property: + attribute = getattr(item, kitem_property) + if key: + if not isinstance(attribute, (HDF5Container, BaseModel)): + response = attribute[int(key)] + else: + response = getattr(attribute, key) + if hasattr(response, "get_unit"): + response = f"{response} {response.get_unit().get('symbol')}" + else: + response = attribute + else: + response = item + click.echo(response) + + +if __name__ == "__main__": + lookup_kitem() diff --git a/dsms/knowledge/properties/custom_datatype/numerical.py b/dsms/knowledge/properties/custom_datatype/numerical.py index 8abac1b..c996774 100644 --- a/dsms/knowledge/properties/custom_datatype/numerical.py +++ b/dsms/knowledge/properties/custom_datatype/numerical.py @@ -1,5 +1,6 @@ """Module for custom numerical data type""" +import logging from typing import TYPE_CHECKING from dsms.knowledge.semantics.units.utils import ( @@ -13,6 +14,9 @@ from dsms import KItem +logger = logging.Logger(__name__) + + class NumericalDataType(float): """Custom Base data type for custom properties""" @@ -20,6 +24,23 @@ def __init__(self, value) -> None: # pylint: disable=unused-argument self._kitem: "Optional[KItem]" = None self._name: "Optional[str]" = None + def __str__(self) -> str: + """Pretty print the numerical datatype""" + if self.kitem.dsms.config.display_units: + try: + string = f"{self.__float__()} {self.get_unit().get('symbol')}" + except Exception as error: + logger.debug( + "Could not fetch unit from `%i`: %i", self.name, error.args + ) + string = str(self.__float__()) + else: + string = str(self.__float__()) + return string + + def __repr__(self) -> str: + return str(self) + @property def kitem(cls) -> "Optional[KItem]": """Context of the current kitem for this property""" @@ -42,7 +63,12 @@ def name(cls, value: str) -> None: def get_unit(self) -> "Dict[str, Any]": """Get unit for the property""" - return get_property_unit(self.kitem.id, self.name) + return get_property_unit( + self.kitem.id, + self.name, + is_hdf5_column=True, + autocomplete_symbol=self.kitem.dsms.config.autocomplete_units, + ) def convert_to( self, diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index 2695de2..cb908d2 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -1,4 +1,5 @@ """HDF5 Properties of a KItem""" +import logging from typing import TYPE_CHECKING import numpy as np @@ -13,6 +14,8 @@ get_property_unit, ) +logger = logging.Logger(__name__) + if TYPE_CHECKING: from typing import Any, Callable, Dict, List, Optional @@ -32,6 +35,22 @@ class Column(KPropertyItem): ..., description="Name of the column in the data series." ) + def __repr__(self) -> str: + """Pretty print the numerical datatype""" + if self.kitem.dsms.config.display_units: + try: + unit = f"\tunit={self.get_unit().get('symbol')}\n\t\t" + string = str(self) + string = string[:-1] + unit + string[-1:] + except Exception as error: + logger.debug( + "Could not fetch unit from `%i`: %i", self.name, error.args + ) + string = str(self) + else: + string = str(self) + return string + def get(self) -> "List[Any]": """ Download the data for the column in a time series. @@ -48,7 +67,12 @@ def get_unit(self) -> "Dict[str, Any]": Returns: Dict[str, Any]: Dictionary containing unit information. """ - return get_property_unit(self.id, self.name, is_hdf5_column=True) + return get_property_unit( + self.id, + self.name, + is_hdf5_column=True, + autocomplete_symbol=self.kitem.dsms.config.autocomplete_units, + ) def convert_to( self, diff --git a/dsms/knowledge/semantics/queries/base.py b/dsms/knowledge/semantics/queries/base.py index d8c65b6..37f1b58 100644 --- a/dsms/knowledge/semantics/queries/base.py +++ b/dsms/knowledge/semantics/queries/base.py @@ -53,6 +53,13 @@ def query(cls) -> str: Define sparql query by using the kwargs defined during initialization. """ + @abstractmethod + def postprocess_result(cls, row: "Dict[str, Any]") -> "Dict[str, Any]": + """ + Define a function that postprocesses the result of the indivudal row in the + sparql result. This might e.g. be some string operations etc. + """ + def execute(self) -> None: """Execute sparql query and bind results.""" result = self.dsms.sparql_interface.query(self.query) @@ -60,8 +67,10 @@ def execute(self) -> None: for row in JSONResult(result).bindings: row_converted = {str(key): value for key, value in row.items()} self._results.append( - { - name: func(row_converted.get(name)) - for name, func in self.result_mappings.items() - } + self.postprocess_result( + { + name: func(row_converted.get(name)) + for name, func in self.result_mappings.items() + } + ) ) diff --git a/dsms/knowledge/semantics/units/base.py b/dsms/knowledge/semantics/units/base.py index 0d68cdf..541d285 100644 --- a/dsms/knowledge/semantics/units/base.py +++ b/dsms/knowledge/semantics/units/base.py @@ -21,11 +21,13 @@ def __init__( kitem_id: "Union[str, UUID]", property_name: str, is_hdf5_column: bool = False, + autocomplete_symbol: bool = True, ) -> None: super().__init__( kitem_id=kitem_id, property_name=property_name, is_hdf5_column=is_hdf5_column, + autocomplete_symbol=autocomplete_symbol, ) # OVERRIDE diff --git a/dsms/knowledge/semantics/units/conversion.py b/dsms/knowledge/semantics/units/conversion.py index e6b92e7..f6f0e98 100644 --- a/dsms/knowledge/semantics/units/conversion.py +++ b/dsms/knowledge/semantics/units/conversion.py @@ -41,6 +41,15 @@ def _qudt_sparql_factor(uri: str) -> str: }}""" +def _sparql_symbol_from_iri(uri: str) -> str: + return f"""PREFIX qudt: + SELECT DISTINCT ?symbol + WHERE {{ + <{uri}> a qudt:Unit ; + qudt:ucumCode ?symbol . + }}""" + + def _qudt_sparql_quantity(original_uri: str, target_uri: str) -> str: return f"""PREFIX qudt: SELECT DISTINCT ?kind @@ -80,6 +89,20 @@ def _check_qudt_mapping(symbol: str) -> Optional[str]: return match.pop() +@lru_cache +def _get_symbol_from_uri(uri: str) -> str: + graph = _get_qudt_graph("qudt_units") + query = _sparql_symbol_from_iri(uri) + symbol = [str(row["symbol"]) for row in graph.query(query)] + if len(symbol) == 0: + raise ValueError(f"No symbol found for unit with uri `{uri}`.") + if len(symbol) > 1: + raise ValueError( + f"More than one symbol factor for unit with uri `{uri}`." + ) + return symbol.pop() + + @lru_cache def _get_factor_from_uri(uri: str) -> int: graph = _get_qudt_graph("qudt_units") diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py index 5fe4b4f..93ecca8 100644 --- a/dsms/knowledge/semantics/units/sparql.py +++ b/dsms/knowledge/semantics/units/sparql.py @@ -1,9 +1,15 @@ """Semantic units for a custom property of a KItem in DSMS""" +from typing import TYPE_CHECKING from urllib.parse import urljoin from dsms.knowledge.semantics.units.base import BaseUnitSparqlQuery +from .conversion import _get_symbol_from_uri + +if TYPE_CHECKING: + from typing import Any, Dict + class UnitSparqlQuery(BaseUnitSparqlQuery): """ @@ -38,3 +44,15 @@ def query(cls) -> str: }} }} }}""" + + # OVERRIDE + def postprocess_result(cls, row: "Dict[str, Any]") -> "Dict[str, Any]": + """ + Define a function that postprocesses the result of the indivudal row in the + sparql result. This might e.g. be some string operations etc. + """ + if row.get("symbol") == "None": + row["symbol"] = None + if cls.kwargs.get("autocomplete_symbol") and not row.get("symbol"): + row["symbol"] = _get_symbol_from_uri(row.get("iri")) + return row diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py index 0ab6638..f689124 100644 --- a/dsms/knowledge/semantics/units/utils.py +++ b/dsms/knowledge/semantics/units/utils.py @@ -69,6 +69,7 @@ def get_property_unit( kitem_id: "Union[str, UUID]", property_name: str, is_hdf5_column: bool = False, + autocomplete_symbol: bool = True, ) -> "Dict[str, Any]": """ Retrieve the unit associated with a given property of a KIitem. @@ -79,6 +80,9 @@ def get_property_unit( the unit is to be retrieved. is_hdf5_column (bool, optional): Indicates whether the property is an HDF5 column or a custom property. Defaults to False. + autocomplete_symbol (bool, optional): Whether the symbol of a unit shall be + fetched automatically from the ontology when it is not given next to the + URI. Returns: Dict[str, Any]: A dictionary with the symbol and iri of the unit associated @@ -96,7 +100,12 @@ def get_property_unit( f"´{units_sparql_object}´ must be a subclass of `{BaseUnitSparqlQuery}`" ) try: - query = units_sparql_object(kitem_id, property_name, is_hdf5_column) + query = units_sparql_object( + kitem_id, + property_name, + is_hdf5_column=is_hdf5_column, + autocomplete_symbol=autocomplete_symbol, + ) except Exception as error: raise ValueError( f"Something went wrong catching the unit for property `{property_name}`." diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index c74f2bb..ae4a869 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -435,7 +435,7 @@ def _search( payload = { "search_term": query or "", "ktypes": [ktype.value for ktype in ktypes], - "kitem_annotations": annotations, + "annotations": annotations, "limit": limit, } response = _perform_request( diff --git a/setup.cfg b/setup.cfg index 4d95dca..4cbf4ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,10 @@ install_requires = python_requires = >=3.8 include_package_data = True +[options.entry_points] +console_scripts = + kitem = dsms.knowledge.cli:lookup_kitem + [options.extras_require] dev = bumpver==2021.1114 From 11de4d51a7d3d61d2f62602a8f4674af7e2ad6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 23 Apr 2024 01:03:04 +0200 Subject: [PATCH 043/114] remove unneeded side effects of kitem properties, update cli, speed up kitem retrieval, update kitem difference detection --- dsms/knowledge/cli.py | 29 ++---- dsms/knowledge/kitem.py | 24 +++-- dsms/knowledge/properties/affiliations.py | 19 ---- dsms/knowledge/properties/annotations.py | 19 ---- dsms/knowledge/properties/apps.py | 19 ---- dsms/knowledge/properties/attachments.py | 19 ---- dsms/knowledge/properties/authors.py | 19 ---- dsms/knowledge/properties/base.py | 76 ++++++---------- dsms/knowledge/properties/contacts.py | 19 ---- dsms/knowledge/properties/external_links.py | 19 ---- dsms/knowledge/properties/hdf5.py | 19 ---- dsms/knowledge/properties/linked_kitems.py | 97 +++++---------------- dsms/knowledge/properties/user_groups.py | 19 ---- dsms/knowledge/semantics/units/utils.py | 4 +- dsms/knowledge/utils.py | 6 +- setup.cfg | 1 + tests/conftest.py | 33 +++++++ 17 files changed, 109 insertions(+), 332 deletions(-) diff --git a/dsms/knowledge/cli.py b/dsms/knowledge/cli.py index 2889616..d52d5fa 100644 --- a/dsms/knowledge/cli.py +++ b/dsms/knowledge/cli.py @@ -2,40 +2,21 @@ import click -from pydantic import BaseModel from dsms import DSMS -from dsms.knowledge.properties import HDF5Container @click.command() @click.argument("kitem-id") -@click.option("-p", "--kitem-property", help="Name of the kitem property") -@click.option("-e", "--env", default=".env", help="Env file to load for the ") @click.option( - "-k", - "--key", - default=None, - help="key of the subproperty of the KItem Property. E.g. a list index or a column name", + "-e", "--env", default=".env", help="Env file to load for the dsms session" ) -def lookup_kitem(kitem_id, kitem_property, env, key): +@click.option("-u", "--units", is_flag=True, help="Display KItem with units") +def lookup_kitem(kitem_id, env, units): """Simple CLI for looking up KItems from the DSMS""" - dsms = DSMS(env=env) + dsms = DSMS(env=env, display_units=units) item = dsms[kitem_id] - if kitem_property: - attribute = getattr(item, kitem_property) - if key: - if not isinstance(attribute, (HDF5Container, BaseModel)): - response = attribute[int(key)] - else: - response = getattr(attribute, key) - if hasattr(response, "get_unit"): - response = f"{response} {response.get_unit().get('symbol')}" - else: - response = attribute - else: - response = item - click.echo(response) + click.echo(item) if __name__ == "__main__": diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index a52f535..8082b77 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -191,7 +191,7 @@ def __init__(self, **kwargs: "Any") -> None: # add kitem to buffer if ( - not _kitem_exists(self) + not _slug_is_available(self._get_ktype_as_str(), self.slug) and self.id not in self.context.buffers.created ): self.context.buffers.created.update({self.id: self}) @@ -299,7 +299,7 @@ def validate_external_links( @field_validator("linked_kitems", mode="before") @classmethod def validate_linked_kitems_list( - cls, value: "List[Union[Dict, KItem, Any]]", info: ValidationInfo + cls, value: "List[Union[Dict, KItem, Any]]" ) -> List[LinkedKItem]: """Validate each single kitem to be linked""" linked_kitems = [] @@ -317,9 +317,7 @@ def validate_linked_kitems_list( raise AttributeError( f"Linked KItem `{item}` has no attribute `id`." ) from error - linked_kitems.append( - LinkedKItem(id=dest_id, source_id=info.data["id"]) - ) + linked_kitems.append(LinkedKItem(id=dest_id)) return linked_kitems @field_validator("linked_kitems", mode="after") @@ -385,12 +383,12 @@ def validate_slug(cls, value: str, info: ValidationInfo) -> str: kitem_id = info.data.get("id") if not value: value = _slugify(name) - if Context.dsms.config.individual_slugs: - value += f"-{str(kitem_id).split('-', maxsplit=1)[0]}" if len(value) < 4: raise ValueError( "Slug length must have a minimum length of 4." ) + if Context.dsms.config.individual_slugs: + value += f"-{str(kitem_id).split('-', maxsplit=1)[0]}" if not _kitem_exists(kitem_id) and not _slug_is_available( ktype_id.value, value ): @@ -500,5 +498,15 @@ def context(cls) -> "Context": def url(cls) -> str: """URL of the KItem""" return urljoin( - str(cls.context.dsms.config.host_url), f"{cls.ktype_id}/{cls.slug}" + str(cls.context.dsms.config.host_url), + f"{cls._get_ktype_as_str()}/{cls.slug}", ) + + def _get_ktype_as_str(self) -> str: + if isinstance(self.ktype_id, str): + ktype = self.ktype_id + elif isinstance(self.ktype_id, Enum): + ktype = self.ktype_id.value # pylint: disable=no-member + else: + raise TypeError(f"Datatype for KType is unknown: {type(ktype)}") + return ktype diff --git a/dsms/knowledge/properties/affiliations.py b/dsms/knowledge/properties/affiliations.py index ead24b6..1abbf53 100644 --- a/dsms/knowledge/properties/affiliations.py +++ b/dsms/knowledge/properties/affiliations.py @@ -23,22 +23,3 @@ class AffiliationsProperty(KProperty): @property def k_property_item(cls) -> "Callable": return Affiliation - - # OVERRIDE - def _add(self, item: Affiliation) -> Affiliation: - """Side effect when an affiliation is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: Affiliation) -> Affiliation: - """Side effect when an affiliation is updated at the KProperty""" - return item - - # OVERRIDE - def _get(self, item: Affiliation) -> Affiliation: - """Side effect when getting the affiliation for a specfic kitem""" - return item - - # OVERRIDE - def _delete(self, item: Affiliation) -> None: - """Side effect when deleting the affiliation of a KItem""" diff --git a/dsms/knowledge/properties/annotations.py b/dsms/knowledge/properties/annotations.py index 27e2974..713fc0b 100644 --- a/dsms/knowledge/properties/annotations.py +++ b/dsms/knowledge/properties/annotations.py @@ -26,22 +26,3 @@ class AnnotationsProperty(KProperty): def k_property_item(cls) -> "Callable": """Annotation data model""" return Annotation - - # OVERRIDE - def _add(self, item: Annotation) -> Annotation: - """Side effect when an Annotation is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: Annotation) -> Annotation: - """Side effect when an Annotation is updated at the KProperty""" - return item - - # OVERRIDE - def _delete(self, item: Annotation) -> None: - """Side effect when deleting the Annotation of a KItem""" - - # OVERRIDE - def _get(self, item: Annotation) -> Annotation: - """Side effect when getting the Annotation for a specfic kitem""" - return item diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 0db76a6..fcd125e 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -67,22 +67,3 @@ class AppsProperty(KProperty): def k_property_item(cls) -> "Callable": """App data model""" return App - - # OVERRIDE - def _add(self, item: App) -> App: - """Side effect when an App is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: App) -> App: - """Side effect when an App is updated at the KProperty""" - return item - - # OVERRIDE - def _delete(self, item: App) -> None: - """Side effect when deleting the App of a KItem""" - - # OVERRIDE - def _get(self, item: App) -> App: - """Side effect when getting the App for a specfic kitem""" - return item diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 425c28e..36b4ec3 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -28,22 +28,3 @@ class AttachmentsProperty(KProperty): @property def k_property_item(cls) -> "Callable": return Attachment - - # OVERRIDE - def _add(self, item: Attachment) -> Attachment: - """Side effect when an Attachment is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: Attachment) -> Attachment: - """Side effect when an Attachment is updated at the KProperty""" - return item - - # OVERRIDE - def _delete(self, item: Attachment) -> None: - """Side effect when deleting the Attachment of a KItem""" - - # OVERRIDE - def _get(self, item: Attachment) -> Attachment: - """Side effect when getting the Attachment for a specfic kitem""" - return item diff --git a/dsms/knowledge/properties/authors.py b/dsms/knowledge/properties/authors.py index e2fd00e..e969bfe 100644 --- a/dsms/knowledge/properties/authors.py +++ b/dsms/knowledge/properties/authors.py @@ -25,22 +25,3 @@ class AuthorsProperty(KProperty): def k_property_item(cls) -> "Callable": """Author data model""" return Author - - # OVERRIDE - def _add(self, item: Author) -> Author: - """Side effect when an Author is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: Author) -> Author: - """Side effect when an Author is updated at the KProperty""" - return item - - # OVERRIDE - def _get(self, item: Author) -> Author: - """Side effect when getting the Author for a specfic kitem""" - return item - - # OVERRIDE - def _delete(self, item: Author) -> None: - """Side effect when deleting the Author of a KItem""" diff --git a/dsms/knowledge/properties/base.py b/dsms/knowledge/properties/base.py index 892c022..779eae2 100644 --- a/dsms/knowledge/properties/base.py +++ b/dsms/knowledge/properties/base.py @@ -12,7 +12,6 @@ ) from dsms.core.utils import _snake_to_camel # isort:skip -from dsms.knowledge.utils import _get_kitem # isort:skip if TYPE_CHECKING: @@ -54,8 +53,8 @@ def __repr__(self) -> str: def __setattr__(self, index: int, item: "Any") -> None: """Add KItem to updated buffer.""" - if self._kitem and self.id not in self.context.buffers.updated: - self.context.buffers.updated.update({self.id: self._kitem}) + if self.kitem and self.kitem.id not in self.context.buffers.updated: + self.context.buffers.updated.update({self.id: self.kitem}) super().__setattr__(index, item) def __hash__(self) -> int: @@ -64,15 +63,28 @@ def __hash__(self) -> int: @property def kitem(cls) -> "KItem": """KItem related to the KPropertyItem""" - if not cls.id: - raise ValueError("KItem not defined yet for KProperty") - return _get_kitem(cls.id) + return cls._kitem + + @kitem.setter + def kitem(cls, item: "KItem") -> None: + """Set KItem related to the KPropertyItem""" + cls._kitem = item + cls.id = item.id @property def exclude(cls) -> "Optional[Set[str]]": """Fields to be excluded from the JSON-schema""" return cls.model_config.get("exclude") + @property + def context(cls) -> "Context": + """Getter for Context""" + from dsms import ( # isort:skip + Context, + ) + + return Context + @model_serializer def serialize(self): """Serialize KPropertItem""" @@ -93,22 +105,6 @@ def __init__(self, *args) -> None: def k_property_item(cls) -> "Callable": """Return the KPropertyItem-class for the KProperty""" - @abstractmethod - def _add(self, item: KPropertyItem) -> KPropertyItem: - """Side effect when an KPropertyItem is added to the KProperty""" - - @abstractmethod - def _update(self, item: KPropertyItem) -> KPropertyItem: - """Side effect when an KPropertyItem is updated in the KProperty""" - - @abstractmethod - def _get(self, item: KPropertyItem) -> KPropertyItem: - """Side effect when an KPropertyItem is retrieved from the KProperty""" - - @abstractmethod - def _delete(self, item: KPropertyItem) -> None: - """Side effect when an KPropertyItem is deleted from the KProperty""" - def __str__(self) -> str: """Pretty print the KProperty""" values = ", \n".join(["\t\t" + repr(value) for value in self]) @@ -129,28 +125,14 @@ def __setitem__( """Add or Update KPropertyItem and add it to the updated-buffer.""" self._mark_as_updated() - item = self._check_k_property_item(item) - if self.kitem: - item.id = self.kitem.id - try: - if self[index] != item: - item = self._update(item) - except IndexError: - item = self._add(item) + item = self._check_item(item) super().__setitem__(index, item) def __delitem__(self, index: int) -> None: """Delete the KPropertyItem from the KProperty""" self._mark_as_updated() - item = super().__delitem__(index) - self._delete(item) - - def __getitem__(self, index: int) -> KPropertyItem: - """Get the KPropertyItem from the KProperty""" - - item = super().__getitem__(index) - return self._get(item) + super().__delitem__(index) def __imul__(self, index: int) -> None: """Imul the KPropertyItem""" @@ -165,10 +147,10 @@ def extend(self, iterable: "Iterable") -> None: for item in iterable: if isinstance(item, (list, tuple)): for subitem in item: - item = self._check_and_add_item(subitem) + item = self._check_item(subitem) to_extend.append(item) elif isinstance(item, (dict, KPropertyItem, KItem)): - item = self._check_and_add_item(item) + item = self._check_item(item) to_extend.append(item) else: to_extend.append(item) @@ -178,14 +160,14 @@ def extend(self, iterable: "Iterable") -> None: def append(self, item: "Union[Dict, Any]") -> None: """Append KPropertyItem to KProperty""" - item = self._check_and_add_item(item) + item = self._check_item(item) self._mark_as_updated() super().append(item) def insert(self, index: int, item: "Union[Dict, Any]") -> None: """Insert KPropertyItem at KProperty at certain index""" - item = self._check_and_add_item(item) + item = self._check_item(item) self._mark_as_updated() super().insert(index, item) @@ -194,21 +176,19 @@ def pop(self, index=-1) -> KPropertyItem: item = super().pop(index) self._mark_as_updated() - self._delete(item) return item def remove(self, item: "Union[Dict, Any]") -> None: """Remove KPropertyItem from KProperty""" self._mark_as_updated() - self._delete(item) super().remove(item) - def _check_and_add_item(self, item: "Union[Dict, Any]") -> KPropertyItem: + def _check_item(self, item: "Union[Dict, Any]") -> KPropertyItem: item = self._check_k_property_item(item) if self.kitem: - item.id = self.kitem.id - return self._add(item) + item.kitem = self.kitem + return item def _check_k_property_item( self, item: "Union[Dict, Any]" @@ -249,7 +229,7 @@ def kitem(cls, value: "KItem") -> None: """KItem setter""" cls._kitem = value for item in cls: - item.id = cls.kitem.id + item.kitem = cls.kitem @property def values(cls) -> "List[Dict[str, Any]]": diff --git a/dsms/knowledge/properties/contacts.py b/dsms/knowledge/properties/contacts.py index 64ac0ba..edcd9e5 100644 --- a/dsms/knowledge/properties/contacts.py +++ b/dsms/knowledge/properties/contacts.py @@ -29,22 +29,3 @@ class ContactsProperty(KProperty): @property def k_property_item(cls) -> "Callable": return ContactInfo - - # OVERRIDE - def _add(self, item: ContactInfo) -> ContactInfo: - """Side effect when a ContactInfo is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: ContactInfo) -> ContactInfo: - """Side effect when a ContactInfo is updated at the KProperty""" - return item - - # OVERRIDE - def _get(self, item: ContactInfo) -> ContactInfo: - """Side effect when getting the ContactInfo for a specfic kitem""" - return item - - # OVERRIDE - def _delete(self, item: ContactInfo) -> None: - """Side effect when deleting the ContactInfo of a KItem""" diff --git a/dsms/knowledge/properties/external_links.py b/dsms/knowledge/properties/external_links.py index eb74c7e..84505da 100644 --- a/dsms/knowledge/properties/external_links.py +++ b/dsms/knowledge/properties/external_links.py @@ -24,22 +24,3 @@ class ExternalLinksProperty(KProperty): @property def k_property_item(cls) -> "Callable": return ExternalLink - - # OVERRIDE - def _add(self, item: ExternalLink) -> ExternalLink: - """Side effect when an ExternalLink is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: ExternalLink) -> ExternalLink: - """Side effect when an ExternalLink is updated at the KProperty""" - return item - - # OVERRIDE - def _get(self, item: ExternalLink) -> ExternalLink: - """Side effect when getting the ExternalLink for a specfic kitem""" - return item - - # OVERRIDE - def _delete(self, item: ExternalLink) -> None: - """Side effect when deleting the ExternalLink of a KItem""" diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index cb908d2..76a527f 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -111,25 +111,6 @@ class HDF5Container(KProperty): def k_property_item(cls) -> "Callable": return Column - # OVERRIDE - def _add(self, item: Column) -> Column: - """Side effect when an Column is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: Column) -> Column: - """Side effect when an Column is updated at the KProperty""" - return item - - # OVERRIDE - def _delete(self, item: Column) -> None: - """Side effect when deleting the Column of a KItem""" - - # OVERRIDE - def _get(self, item: Column) -> Column: - """Side effect when getting the Column for a specfic kitem""" - return item - def to_df(self) -> pd.DataFrame: """Return hdf5 as pandas DataFrame""" data = {column.name: column.get() for column in self} diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index 67b5f00..4b8744a 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -4,13 +4,13 @@ from typing import TYPE_CHECKING, Optional from uuid import UUID -from pydantic import ConfigDict, Field, model_serializer +from pydantic import ConfigDict, Field, PrivateAttr, model_serializer from dsms.knowledge.properties.base import KProperty, KPropertyItem from dsms.knowledge.utils import _get_kitem if TYPE_CHECKING: - from typing import Any, Callable, Dict, Union + from typing import Callable, Union from dsms import KItem @@ -23,33 +23,31 @@ class LinkedKItem(KPropertyItem): None, description="ID of the KItem to be linked", ) - source_id: Optional[UUID] = Field( - None, description="Source ID of the KItem" - ) - # OVERRIDE - model_config = ConfigDict(exclude={}) + _kitem = PrivateAttr(default=None) # OVERRIDE - def __setattr__(self, index: int, item: "Any") -> None: - """Add KItem to updated buffer.""" - if self._kitem and self.source_id not in self.context.buffers.updated: - self.context.buffers.updated.update({self.source_id: self._kitem}) - super().__setattr__(index, item) + model_config = ConfigDict(exclude={}) # OVERRIDE @property def kitem(cls) -> "KItem": - """KItem related to the KPropertyItem""" - if not cls.id: - raise ValueError("KItem not defined yet for KProperty") - return _get_kitem(cls.source_id) + """KItem related to the linked KItem""" + return cls._kitem + + # OVERRIDE + @kitem.setter + def kitem(cls, value: "KItem") -> None: + """Set KItem related to the linked KItem""" + cls._kitem = value # OVERRIDE @model_serializer def serialize(self): """Serialize KPropertItem""" - return {key: str(value) for key, value in self.__dict__.items()} + base = {key: str(value) for key, value in self.__dict__.items()} + base.update(source_id=str(self.kitem.id)) + return base class LinkedKItemsProperty(KProperty): @@ -60,63 +58,8 @@ class LinkedKItemsProperty(KProperty): def k_property_item(cls) -> "Callable": return LinkedKItem - # OVERRIDE - def _add(self, item: LinkedKItem) -> LinkedKItem: - """Side effect when an LinkedKItem is added to the KProperty""" - if self.kitem: - item = self.k_property_item(id=item.id, source_id=self.kitem.id) - else: - item = self.k_property_item(id=item.id) - return item - - # OVERRIDE - def _update(self, item: LinkedKItem) -> LinkedKItem: - """Side effect when an LinkedKItem is updated at the KProperty""" - return item - - # OVERRIDE - def _get(self, item: LinkedKItem) -> LinkedKItem: - """Side effect when getting the LinkedKItem for a specfic kitem""" - return _get_kitem(item.id) - - # OVERRIDE - def _delete(self, item: LinkedKItem) -> None: - """Side effect when deleting the LinkedKItem of a KItem""" - - # OVERRIDE - def __setitem__( - self, index: int, item: "Union[Dict, KPropertyItem]" - ) -> None: - """Add or Update KPropertyItem and add it to the updated-buffer.""" - - self._mark_as_updated() - item = self._check_k_property_item(item) - if self.kitem: - item.source_id = self.kitem.id - try: - if self[index] != item: - item = self._update(item) - except IndexError: - item = self._add(item) - super().super().__setitem__(index, item) # pylint: disable=no-member - - # OVERRIDE - def _check_and_add_item(self, item: "Union[Dict, Any]") -> KPropertyItem: - item = self._check_k_property_item(item) - if self.kitem: - item.source_id = self.kitem.id - return self._add(item) - - # OVERRIDE - @property - def kitem(cls) -> "KItem": - """KItem context of the field""" - return cls._kitem - - # OVERRIDE - @kitem.setter - def kitem(cls, value: "KItem") -> None: - """KItem setter""" - cls._kitem = value - for item in cls: - item.source_id = cls.kitem.id + def get(self, kitem_id: "Union[str, UUID]") -> "KItem": + """Get the kitem with a certain id which is linked to the source KItem.""" + if not str(kitem_id) in [str(item.id) for item in self]: + raise KeyError(f"A KItem with ID `{kitem_id} is not linked.") + return _get_kitem(kitem_id) diff --git a/dsms/knowledge/properties/user_groups.py b/dsms/knowledge/properties/user_groups.py index 8e0f2fc..1ef690b 100644 --- a/dsms/knowledge/properties/user_groups.py +++ b/dsms/knowledge/properties/user_groups.py @@ -24,22 +24,3 @@ class UserGroupsProperty(KProperty): def k_property_item(cls) -> "Callable": """UserGroup data model""" return UserGroup - - # OVERRIDE - def _add(self, item: UserGroup) -> UserGroup: - """Side effect when an UserGroup is added to the KProperty""" - return item - - # OVERRIDE - def _update(self, item: UserGroup) -> UserGroup: - """Side effect when an UserGroup is updated at the KProperty""" - return item - - # OVERRIDE - def _get(self, item: UserGroup) -> UserGroup: - """Side effect when getting the UserGroup for a specfic kitem""" - return item - - # OVERRIDE - def _delete(self, item: UserGroup) -> None: - """Side effect when deleting the UserGroup of a KItem""" diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py index f689124..8a2de98 100644 --- a/dsms/knowledge/semantics/units/utils.py +++ b/dsms/knowledge/semantics/units/utils.py @@ -101,8 +101,8 @@ def get_property_unit( ) try: query = units_sparql_object( - kitem_id, - property_name, + kitem_id=kitem_id, + property_name=property_name, is_hdf5_column=is_hdf5_column, autocomplete_symbol=autocomplete_symbol, ) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index ae4a869..d940f38 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -375,11 +375,13 @@ def _get_kitems_diffs(kitem_old: "KItem", kitem_new: "KItem"): new_attr = getattr(kitem_new, name) differences[to_add_name] = [ json.loads(attr.model_dump_json()) - for attr in set(new_attr) - set(old_attr) + for attr in new_attr + if new_attr not in old_attr ] differences[to_remove_name] = [ json.loads(attr.model_dump_json()) - for attr in set(old_attr) - set(new_attr) + for attr in old_attr + if old_attr not in new_attr ] return differences diff --git a/setup.cfg b/setup.cfg index 4cbf4ec..e6d5d61 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ classifiers = [options] packages = find: install_requires = + click>=8,<9 html5lib>=1,<2 lru-cache<1 pandas>=2,<3 diff --git a/tests/conftest.py b/tests/conftest.py index cb920ed..00a5b24 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,8 +30,16 @@ class MockDB: "name": "bar123", "id": "698acdc5-dd97-4217-906e-2c0b44248c17", }, + "cc834fb6-d4cd-43c1-b4b8-4c720ac6f038": { + "name": "bar456", + "id": "cc834fb6-d4cd-43c1-b4b8-4c720ac6f038", + }, } + slugs = [ + value.get("name") + "-" + key[:8] for key, value in kitems.items() + ] + hdf5 = { "698acdc5-dd97-4217-906e-2c0b44248c17": [ { @@ -114,6 +122,14 @@ def return_hdf5(request): else: return 200, {}, json.dumps(MockDB.hdf5[item_id]) + def return_slugs(request): + url_parts = request.url.split("/") + slug = url_parts[-1] + if slug not in MockDB.slugs: + return 404, {}, "Slug does not exist" + else: + return 200, {}, "Slug exists" + def _get_kitems() -> "Dict[str, Any]": return { urljoin(custom_address, f"api/knowledge/kitems/{uid}"): [ @@ -142,6 +158,22 @@ def _get_hdf5() -> "Dict[str, Any]": for uid in MockDB.kitems } + def _get_slugs() -> "Dict[str, Any]": + return { + urljoin( + custom_address, f"api/knowledge/kitems/organization/{slug}" + ): [ + { + "method": responses.HEAD, + "returns": { + "content_type": "application/json", + "callback": return_slugs, + }, + } + ] + for slug in MockDB.slugs + } + return { ktypes: [ { @@ -154,6 +186,7 @@ def _get_hdf5() -> "Dict[str, Any]": ], **_get_kitems(), **_get_hdf5(), + **_get_slugs(), } From c6df1bcf081f424d443e5af08495bb4933d11f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 23 Apr 2024 02:25:48 +0200 Subject: [PATCH 044/114] update buffering of kitems --- dsms/knowledge/kitem.py | 16 ++++---- dsms/knowledge/properties/attachments.py | 47 +++++++++++++++++++++++- dsms/knowledge/properties/base.py | 25 ++++++++----- dsms/knowledge/utils.py | 12 +++++- 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 8082b77..f98feaa 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -191,7 +191,7 @@ def __init__(self, **kwargs: "Any") -> None: # add kitem to buffer if ( - not _slug_is_available(self._get_ktype_as_str(), self.slug) + _slug_is_available(self._get_ktype_as_str(), self.slug) and self.id not in self.context.buffers.created ): self.context.buffers.created.update({self.id: self}) @@ -228,7 +228,7 @@ def __repr__(self) -> str: def __hash__(self) -> int: return hash(str(self)) - @field_validator("affiliations") + @field_validator("affiliations", mode="after") @classmethod def validate_affiliation( cls, value: List[Affiliation] @@ -236,7 +236,7 @@ def validate_affiliation( """Validate affiliations Field""" return AffiliationsProperty(value) - @field_validator("annotations") + @field_validator("annotations", mode="after") @classmethod def validate_annotations( cls, value: List[Annotation] @@ -265,7 +265,7 @@ def validate_attachments_after( """Validate attachments Field""" return AttachmentsProperty(value) - @field_validator("kitem_apps") + @field_validator("kitem_apps", mode="after") @classmethod def validate_apps(cls, value: List[App]) -> AppsProperty: """Validate apps Field""" @@ -282,13 +282,13 @@ def validate_authors(cls, value: List[Author]) -> AuthorsProperty: ] ) - @field_validator("contacts") + @field_validator("contacts", mode="after") @classmethod def validate_contacts(cls, value: List[ContactInfo]) -> ContactsProperty: """Validate contacts Field""" return ContactsProperty(value) - @field_validator("external_links") + @field_validator("external_links", mode="after") @classmethod def validate_external_links( cls, value: List[ExternalLink] @@ -329,7 +329,7 @@ def validate_linked_kitems( """Validate the list out of linked KItems""" return LinkedKItemsProperty(value) - @field_validator("user_groups") + @field_validator("user_groups", mode="after") @classmethod def validate_user_groups( cls, value: List[UserGroup] @@ -499,7 +499,7 @@ def url(cls) -> str: """URL of the KItem""" return urljoin( str(cls.context.dsms.config.host_url), - f"{cls._get_ktype_as_str()}/{cls.slug}", + f"knowledge/{cls._get_ktype_as_str()}/{cls.slug}", ) def _get_ktype_as_str(self) -> str: diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 36b4ec3..1cfe093 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -1,5 +1,6 @@ """Attachment KProperty""" +from pathlib import Path from typing import TYPE_CHECKING from pydantic import Field @@ -8,7 +9,7 @@ from dsms.knowledge.utils import _get_attachment if TYPE_CHECKING: - from typing import Callable + from typing import Any, Callable, Dict, Iterable, List, Union class Attachment(KPropertyItem): @@ -28,3 +29,47 @@ class AttachmentsProperty(KProperty): @property def k_property_item(cls) -> "Callable": return Attachment + + def extend(self, iterable: "Iterable") -> None: + """Extend KProperty with list of KPropertyItem""" + from dsms import KItem + + to_extend = [] + for item in iterable: + if isinstance(item, (list, tuple)): + for subitem in item: + item = self._check_item(subitem) + if not Path(item.name).stem in self.by_name: + to_extend.append(item) + elif isinstance(item, (dict, KPropertyItem, KItem)): + item = self._check_item(item) + if not Path(item.name).stem in self.by_name: + to_extend.append(item) + else: + if not Path(item.name).stem in self.by_name: + to_extend.append(item) + if to_extend: + self._mark_as_updated() + super().extend(to_extend) + + def append(self, item: "Union[Dict, Any]") -> None: + """Append KPropertyItem to KProperty""" + + item = self._check_item(item) + + if not Path(item.name).stem in self.by_name: + self._mark_as_updated() + super().append(item) + + def insert(self, index: int, item: "Union[Dict, Any]") -> None: + """Insert KPropertyItem at KProperty at certain index""" + + item = self._check_item(item) + if not Path(item.name).stem in self.by_name: + self._mark_as_updated() + super().insert(index, item) + + @property + def by_name(cls) -> "List[str]": + "Return list of names of attachments" + return {Path(attachment.name).stem: attachment for attachment in cls} diff --git a/dsms/knowledge/properties/base.py b/dsms/knowledge/properties/base.py index 779eae2..16518b7 100644 --- a/dsms/knowledge/properties/base.py +++ b/dsms/knowledge/properties/base.py @@ -148,28 +148,35 @@ def extend(self, iterable: "Iterable") -> None: if isinstance(item, (list, tuple)): for subitem in item: item = self._check_item(subitem) - to_extend.append(item) + if not item in self: + to_extend.append(item) elif isinstance(item, (dict, KPropertyItem, KItem)): item = self._check_item(item) - to_extend.append(item) + if not item in self: + to_extend.append(item) else: - to_extend.append(item) - self._mark_as_updated() - super().extend(to_extend) + if not item in self: + to_extend.append(item) + if to_extend: + self._mark_as_updated() + super().extend(to_extend) def append(self, item: "Union[Dict, Any]") -> None: """Append KPropertyItem to KProperty""" item = self._check_item(item) - self._mark_as_updated() - super().append(item) + + if not item in self: + self._mark_as_updated() + super().append(item) def insert(self, index: int, item: "Union[Dict, Any]") -> None: """Insert KPropertyItem at KProperty at certain index""" item = self._check_item(item) - self._mark_as_updated() - super().insert(index, item) + if not item in self: + self._mark_as_updated() + super().insert(index, item) def pop(self, index=-1) -> KPropertyItem: """Pop KPropertyItem from KProperty""" diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index d940f38..348e76c 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -354,8 +354,16 @@ def _get_attachment(kitem_id: "KItem", file_name: str) -> str: def _get_attachment_diffs(kitem_old: "KItem", kitem_new: "KItem"): """Check which attachments should be removed and which should be added.""" return { - "remove": set(kitem_old.attachments) - set(kitem_new.attachments), - "add": set(kitem_new.attachments) - set(kitem_old.attachments), + "remove": [ + attachment + for name, attachment in kitem_old.attachments.by_name.items() + if name not in kitem_new.attachments.by_name + ], + "add": [ + attachment + for name, attachment in kitem_new.attachments.by_name.items() + if name not in kitem_old.attachments.by_name + ], } From 20bdcc519897a3d08cadfac3609cbce3341e5086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 23 Apr 2024 02:32:05 +0200 Subject: [PATCH 045/114] Bump version v1.4.0rc7 -> v1.4.0rc8 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e6d5d61..142d1b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc7 +version = v1.4.0rc8 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From ae29e92649dd784f268cc8ea9f787a8bc7086bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 23 Apr 2024 10:12:04 +0200 Subject: [PATCH 046/114] minor change in ktype retrieval --- dsms/knowledge/kitem.py | 11 +++++++++-- dsms/knowledge/utils.py | 13 ------------- examples/basic_usage.ipynb | 2 -- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index f98feaa..fba4917 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -52,7 +52,6 @@ _slug_is_available, _slugify, _inspect_hdf5, - _get_ktype_from_id, ) from dsms.knowledge.sparql_interface.utils import _get_subgraph # isort:skip @@ -365,11 +364,19 @@ def validate_updated(cls, value: str) -> Any: @classmethod def validate_ktype(cls, value: KType, info: ValidationInfo) -> KType: """Validate the data attribute of the KItem""" + from dsms import Context if not value: ktype_id = info.data.get("ktype_id") - value = _get_ktype_from_id(ktype_id) + if not isinstance(ktype_id, str): + value = Context.ktypes.get(ktype_id.value) + else: + value = Context.ktypes.get(ktype_id) + if not value: + raise TypeError( + f"KType for `ktype_id={ktype_id}` does not exist." + ) return value @field_validator("slug") diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 348e76c..4032fa7 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -151,19 +151,6 @@ def _validate_model( return values -def _get_ktype_from_id(ktype_id: str) -> "KType": - from dsms import Context - - if not isinstance(ktype_id, str): - value = Context.ktypes.get(ktype_id.value) - else: - value = Context.ktypes.get(ktype_id) - - if not value: - raise TypeError(f"KType for `ktype_id={ktype_id}` does not exist.") - return value - - def _get_remote_ktypes() -> Enum: """Get the KTypes from the remote backend""" from dsms import ( # isort:skip diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index e44adc4..76ec48e 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -23,8 +23,6 @@ "outputs": [], "source": [ "import os\n", - "from pprint import pprint\n", - "\n", "\n", "from dsms import DSMS, KItem" ] From d7312574cd93153c090ec1d51cade78177b942bf Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Tue, 23 Apr 2024 15:41:08 +0200 Subject: [PATCH 047/114] Completion of Installation and Introduction md --- .../source/assets/Dummy_Tensile_Test_Data.csv | 31 +++++ .../assets/Dummy_Tensile_Test_Data.xlsx | Bin 0 -> 6724 bytes docs/source/assets/images/dsms_picture_01.jpg | Bin 0 -> 60466 bytes docs/source/installation.md | 27 +++- docs/source/introduction.md | 43 ++++++- docs/src/Makefile | 20 --- docs/src/dsms.apps.rst | 29 ----- docs/src/dsms.core.rst | 45 ------- docs/src/dsms.knowledge.properties.rst | 117 ------------------ docs/src/dsms.knowledge.rst | 46 ------- docs/src/dsms.knowledge.sparql_interface.rst | 37 ------ docs/src/dsms.rst | 20 --- docs/src/make.bat | 35 ------ docs/src/modules.rst | 9 -- docs/src/setup.rst | 7 -- docs/src/source/conf.py | 28 ----- docs/src/source/index.rst | 20 --- docs/src/tests.rst | 37 ------ 18 files changed, 96 insertions(+), 455 deletions(-) create mode 100644 docs/source/assets/Dummy_Tensile_Test_Data.csv create mode 100644 docs/source/assets/Dummy_Tensile_Test_Data.xlsx create mode 100644 docs/source/assets/images/dsms_picture_01.jpg delete mode 100644 docs/src/Makefile delete mode 100644 docs/src/dsms.apps.rst delete mode 100644 docs/src/dsms.core.rst delete mode 100644 docs/src/dsms.knowledge.properties.rst delete mode 100644 docs/src/dsms.knowledge.rst delete mode 100644 docs/src/dsms.knowledge.sparql_interface.rst delete mode 100644 docs/src/dsms.rst delete mode 100644 docs/src/make.bat delete mode 100644 docs/src/modules.rst delete mode 100644 docs/src/setup.rst delete mode 100644 docs/src/source/conf.py delete mode 100644 docs/src/source/index.rst delete mode 100644 docs/src/tests.rst diff --git a/docs/source/assets/Dummy_Tensile_Test_Data.csv b/docs/source/assets/Dummy_Tensile_Test_Data.csv new file mode 100644 index 0000000..9a331bd --- /dev/null +++ b/docs/source/assets/Dummy_Tensile_Test_Data.csv @@ -0,0 +1,31 @@ +Test ID,Material,Load (kN),Strain (%),Stress (MPa),Elongation (mm),Failure Mode +1,Steel,17.63,6.74,131.35,3.09,Brittle +2,Copper,7.93,2.18,326.25,3.16,Brittle +3,Aluminum,125.73,1.38,196.16,3.08,Mixed +4,Steel,117.83,3.22,336.91,1.5,Ductile +5,Copper,131.15,3.7,194.98,4.79,Ductile +6,Copper,146.9,5.74,354.39,2.51,Brittle +7,Copper,120.88,4.44,431.41,4.31,Ductile +8,Copper,71.91,9.88,151.32,3.65,Mixed +9,Aluminum,118.18,1.11,653.98,1.84,Mixed +10,Copper,22.15,2.17,553.28,4.16,Ductile +11,Aluminum,97.79,1.7,312.31,2.28,Mixed +12,Titanium,25.79,6.57,518.6,4.46,Mixed +13,Steel,141.98,2.61,175.15,3.12,Mixed +14,Copper,80.67,4.72,560.76,4.47,Brittle +15,Titanium,65.13,2.52,843.44,3.62,Brittle +16,Steel,43.36,1.67,354.86,3.76,Ductile +17,Steel,117.26,1.19,633.93,2.76,Ductile +18,Steel,71.14,6.6,205.44,4.8,Ductile +19,Titanium,87.42,1.47,673.06,3.4,Mixed +20,Aluminum,7.72,2.05,331.52,2.41,Mixed +21,Titanium,94.56,3.75,246.55,3.23,Brittle +22,Copper,93.75,8.23,569.21,0.59,Brittle +23,Copper,94.46,1.06,116.09,1.86,Ductile +24,Titanium,141.84,8.4,763.15,3.47,Ductile +25,Steel,103.86,1.05,103.76,1.81,Mixed +26,Aluminum,57.13,9.77,642.25,3.28,Brittle +27,Aluminum,68.37,4.74,316.01,2.43,Brittle +28,Aluminum,106.16,9.77,688.16,1.11,Mixed +29,Aluminum,13.73,6.09,869.75,1.84,Ductile +30,Steel,101.68,7.42,299.0,3.06,Brittle diff --git a/docs/source/assets/Dummy_Tensile_Test_Data.xlsx b/docs/source/assets/Dummy_Tensile_Test_Data.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d5bf8d41783b6f85328a04f1acbe7267bf3261c6 GIT binary patch literal 6724 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 literal 0 HcmV?d00001 diff --git a/docs/source/assets/images/dsms_picture_01.jpg b/docs/source/assets/images/dsms_picture_01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a360c15be5ab1486ef53562b53dce2cf161fadbd GIT binary patch literal 60466 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

    @&s|@>)qzW4J&|IHKrMQ=y)H18Fr;q`g7?E8# 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 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/src/modules.rst b/docs/src/modules.rst deleted file mode 100644 index 1d09d62..0000000 --- a/docs/src/modules.rst +++ /dev/null @@ -1,9 +0,0 @@ -dsms-python-sdk -=============== - -.. toctree:: - :maxdepth: 4 - - dsms - setup - tests diff --git a/docs/src/setup.rst b/docs/src/setup.rst deleted file mode 100644 index 552eb49..0000000 --- a/docs/src/setup.rst +++ /dev/null @@ -1,7 +0,0 @@ -setup module -============ - -.. automodule:: setup - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/source/conf.py b/docs/src/source/conf.py deleted file mode 100644 index 134b313..0000000 --- a/docs/src/source/conf.py +++ /dev/null @@ -1,28 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = 'DSMS-SDK-Documentation' -copyright = '2024, Priyabrat Mishra' -author = 'Priyabrat Mishra' -release = '1.0.0' - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = [] - -templates_path = ['_templates'] -exclude_patterns = [] - - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = 'alabaster' -html_static_path = ['_static'] diff --git a/docs/src/source/index.rst b/docs/src/source/index.rst deleted file mode 100644 index 1cdad64..0000000 --- a/docs/src/source/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. DSMS-SDK-Documentation documentation master file, created by - sphinx-quickstart on Thu Apr 11 19:52:57 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to DSMS-SDK-Documentation's documentation! -================================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/src/tests.rst b/docs/src/tests.rst deleted file mode 100644 index ba28f63..0000000 --- a/docs/src/tests.rst +++ /dev/null @@ -1,37 +0,0 @@ -tests package -============= - -Submodules ----------- - -tests.conftest module ---------------------- - -.. automodule:: tests.conftest - :members: - :undoc-members: - :show-inheritance: - -tests.test\_kitem module ------------------------- - -.. automodule:: tests.test_kitem - :members: - :undoc-members: - :show-inheritance: - -tests.test\_utils module ------------------------- - -.. automodule:: tests.test_utils - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: tests - :members: - :undoc-members: - :show-inheritance: From 509221a535f4c2ab715b2aee39e33b11050f800c Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Tue, 23 Apr 2024 15:22:08 +0000 Subject: [PATCH 048/114] Real examples for tutorials --- docs/source/tutorials/2_creation.ipynb | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index 8c26bd5..b49d6fd 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -77,7 +77,15 @@ "metadata": {}, "source": [ "We can make new KItems by simple class-initiation: (Make Sure existing KItems is not put as input). \n", - "#" + "\n", + "Below code makes a simple KItem with following details:\n", + "- **Name** - TestSpecimens\n", + "- **KType** - DatasetCatalog\n", + "- **Custom Properties** \n", + " - ***Shape*** : Cylindrical\n", + " - ***Dimensions*** : 150 x 20 x 40 cm\n", + " - ***Material*** : Metal\n", + " - ***Project ID***: StrengthTesting2024" ] }, { @@ -110,9 +118,14 @@ ], "source": [ "item = KItem(\n", - " name=\"foo123\",\n", + " name=\"TestSpecimens\",\n", " ktype_id=dsms.ktypes.DatasetCatalog,\n", - " custom_properties={\"foo\": \"bar\"},\n", + " custom_properties={\n", + " \"Shape\": \"Cylindrical\",\n", + " \"Dimensions\": \"150 x 20 x 40 cm\",\n", + " \"Material\": \"Metal\",\n", + " \"Project ID\": \"StrengthTesting2024\"\n", + " }\n", ")\n", "\n", "item" @@ -183,7 +196,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.1.0" } }, "nbformat": 4, From 319d3783c001e106f1a10eb70abd82a95496fd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 23 Apr 2024 23:01:33 +0200 Subject: [PATCH 049/114] update commiting mechanism and clear bugs --- .gitignore | 2 + dsms/core/configuration.py | 26 +- dsms/knowledge/kitem.py | 76 +- dsms/knowledge/properties/affiliations.py | 6 + dsms/knowledge/properties/annotations.py | 13 +- dsms/knowledge/properties/apps.py | 4 + dsms/knowledge/properties/attachments.py | 7 + dsms/knowledge/properties/authors.py | 5 + dsms/knowledge/properties/base.py | 45 +- dsms/knowledge/properties/contacts.py | 5 + dsms/knowledge/properties/external_links.py | 5 + dsms/knowledge/properties/hdf5.py | 5 + dsms/knowledge/properties/linked_kitems.py | 14 + dsms/knowledge/properties/user_groups.py | 4 + dsms/knowledge/properties/utils.py | 10 + dsms/knowledge/utils.py | 91 +- examples/basic_usage.ipynb | 882 +++++++++++--------- 17 files changed, 714 insertions(+), 486 deletions(-) create mode 100644 dsms/knowledge/properties/utils.py diff --git a/.gitignore b/.gitignore index 68bc17f..23b4763 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.vscode/ diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 36eb8b4..50892e5 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -2,7 +2,7 @@ import urllib import warnings -from typing import TYPE_CHECKING, Optional +from typing import Callable, List, Optional, Set, Union import requests from pydantic import AnyUrl, Field, SecretStr, field_validator @@ -11,9 +11,6 @@ from .utils import get_callable -if TYPE_CHECKING: - from typing import Callable - MODULE_REGEX = r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*:[a-zA-Z_][a-zA-Z0-9_]*$" DEFAULT_UNIT_SPARQL = "dsms.knowledge.semantics.units.sparql:UnitSparqlQuery" DEFAULT_REPO = "knowledge-items" @@ -106,11 +103,32 @@ class Configuration(BaseSettings): the units of a HDF5 column/ custom property of a KItem.""", ) + filter_bad_hdf5_types: Optional[List[Callable]] = Field( + [str, type(None)], + description="""A list of classes which should be filtered out + from a column of an hdf5 when fetched""", + ) + + hide_properties: Set[Union[str, None]] = Field( + set(), + description="Properties to hide while printing, e.g {'external_links'}", + ) + @field_validator("units_sparql_object") def get_unit_sparql_object(cls, val: str) -> "Callable": """Source the class from the given module""" return get_callable(val) + @field_validator("hide_properties") + def validate_hide_properties(cls, val: Set) -> "Callable": + """Source the class from the given module""" + from dsms import KItem + + for key in val: + if key not in KItem.model_fields: + raise KeyError(f"Property `{key}` not in KItem schema") + return val + @field_validator("token") def validate_auth(cls, val, info: ValidationInfo): """Validate the provided authentication/authorization secrets.""" diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index fba4917..27a4476 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -52,6 +52,7 @@ _slug_is_available, _slugify, _inspect_hdf5, + _make_annotation_schema, ) from dsms.knowledge.sparql_interface.utils import _get_subgraph # isort:skip @@ -112,6 +113,10 @@ class KItem(BaseModel): description="ID of the KItem", ) ktype_id: Union[Enum, str] = Field(..., description="Type ID of the KItem") + in_backend: bool = Field( + False, + description="Whether the KItem was already created in the backend.", + ) slug: Optional[str] = Field( None, description="Slug of the KContext.dsms", min_length=4 ) @@ -169,6 +174,10 @@ class KItem(BaseModel): Union[List[Column], pd.DataFrame, Dict[str, Union[List, Dict]]] ] = Field(None, description="HDF5 interface.") + rdf_exists: bool = Field( + False, description="Whether the KItem holds an RDF Graph or not." + ) + model_config = ConfigDict( extra="forbid", validate_assignment=True, @@ -189,10 +198,7 @@ def __init__(self, **kwargs: "Any") -> None: super().__init__(**kwargs) # add kitem to buffer - if ( - _slug_is_available(self._get_ktype_as_str(), self.slug) - and self.id not in self.context.buffers.created - ): + if not self.in_backend and self.id not in self.context.buffers.created: self.context.buffers.created.update({self.id: self}) self.context.buffers.updated.update({self.id: self}) @@ -215,7 +221,10 @@ def __str__(self) -> str: [ f"\n\t{key} = {value}" for key, value in self.__dict__.items() - if key not in self.model_config["exclude"] + if ( + key not in self.model_config["exclude"] + and key not in self.dsms.config.hide_properties + ) ] ) return f"{self.__class__.__name__}(\n{fields}\n)" @@ -227,17 +236,43 @@ def __repr__(self) -> str: def __hash__(self) -> int: return hash(str(self)) + @field_validator("affiliations", mode="before") + @classmethod + def validate_affiliations_before( + cls, value: List[Union[str, Affiliation]] + ) -> List[Affiliation]: + """Validate affiliations Field""" + return [ + Affiliation(name=affiliation) + if isinstance(affiliation, str) + else affiliation + for affiliation in value + ] + @field_validator("affiliations", mode="after") @classmethod - def validate_affiliation( + def validate_affiliation_after( cls, value: List[Affiliation] ) -> AffiliationsProperty: """Validate affiliations Field""" return AffiliationsProperty(value) + @field_validator("annotations", mode="before") + @classmethod + def validate_annotations_before( + cls, value: List[Union[str, Annotation]] + ) -> List[Annotation]: + """Validate annotations Field""" + return [ + Annotation(**_make_annotation_schema(annotation)) + if isinstance(annotation, str) + else annotation + for annotation in value + ] + @field_validator("annotations", mode="after") @classmethod - def validate_annotations( + def validate_annotations_after( cls, value: List[Annotation] ) -> AnnotationsProperty: """Validate annotations Field""" @@ -379,6 +414,15 @@ def validate_ktype(cls, value: KType, info: ValidationInfo) -> KType: ) return value + @field_validator("in_backend") + @classmethod + def validate_in_backend(cls, value: bool, info: ValidationInfo) -> bool: + """Checks whether the kitem already exists""" + kitem_id = info.data["id"] + if _kitem_exists(kitem_id): + value = True + return value + @field_validator("slug") @classmethod def validate_slug(cls, value: str, info: ValidationInfo) -> str: @@ -386,8 +430,15 @@ def validate_slug(cls, value: str, info: ValidationInfo) -> str: from dsms import Context ktype_id = info.data["ktype_id"] - name = info.data.get("name") kitem_id = info.data.get("id") + kitem_exists = info.data.get("in_backend") + + if not isinstance(ktype_id, str): + ktype = ktype_id.value + else: + ktype = ktype_id + name = info.data.get("name") + if not value: value = _slugify(name) if len(value) < 4: @@ -396,9 +447,7 @@ def validate_slug(cls, value: str, info: ValidationInfo) -> str: ) if Context.dsms.config.individual_slugs: value += f"-{str(kitem_id).split('-', maxsplit=1)[0]}" - if not _kitem_exists(kitem_id) and not _slug_is_available( - ktype_id.value, value - ): + if not kitem_exists and not _slug_is_available(ktype, value): raise ValueError(f"Slug for `{value}` is already taken.") return value @@ -461,7 +510,12 @@ def validate_custom_properties(cls, self) -> "KItem": raise TypeError( f"Invalid type: {type(content)}" ) from error + was_in_buffer = self.id in self.context.buffers.updated self.custom_properties = self.ktype.webform(**content) + # fix: find a better way to prehebit that properties are + # set in the buffer + if not was_in_buffer: + self.context.buffers.updated.pop(self.id) # set kitem id for custom properties if isinstance(self.custom_properties, BaseModel): self.custom_properties.kitem = self diff --git a/dsms/knowledge/properties/affiliations.py b/dsms/knowledge/properties/affiliations.py index 1abbf53..98a56ee 100644 --- a/dsms/knowledge/properties/affiliations.py +++ b/dsms/knowledge/properties/affiliations.py @@ -5,6 +5,7 @@ from pydantic import Field from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.utils import _str_to_dict if TYPE_CHECKING: from typing import Callable @@ -23,3 +24,8 @@ class AffiliationsProperty(KProperty): @property def k_property_item(cls) -> "Callable": return Affiliation + + @property + def k_property_helper(cls) -> "Callable": + """Affiliation property helper""" + return _str_to_dict diff --git a/dsms/knowledge/properties/annotations.py b/dsms/knowledge/properties/annotations.py index 713fc0b..568afa8 100644 --- a/dsms/knowledge/properties/annotations.py +++ b/dsms/knowledge/properties/annotations.py @@ -5,9 +5,10 @@ from pydantic import Field from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.utils import _make_annotation_schema if TYPE_CHECKING: - from typing import Callable + from typing import Any, Callable, Dict class Annotation(KPropertyItem): @@ -26,3 +27,13 @@ class AnnotationsProperty(KProperty): def k_property_item(cls) -> "Callable": """Annotation data model""" return Annotation + + @property + def k_property_helper(cls) -> None: + """Not defined for Affiliations""" + return _make_annotation_schema + + @property + def by_iri(cls) -> "Dict[str, Any]": + """Return dict of annotations per IRI""" + return {annotation.iri for annotation in cls} diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index fcd125e..c1c6e65 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -67,3 +67,7 @@ class AppsProperty(KProperty): def k_property_item(cls) -> "Callable": """App data model""" return App + + @property + def k_property_helper(cls) -> None: + """Not defined for Apps""" diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 1cfe093..9a043ab 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -6,6 +6,7 @@ from pydantic import Field from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.utils import _str_to_dict from dsms.knowledge.utils import _get_attachment if TYPE_CHECKING: @@ -30,6 +31,12 @@ class AttachmentsProperty(KProperty): def k_property_item(cls) -> "Callable": return Attachment + # OVERRIDE + @property + def k_property_helper(cls) -> "Callable": + """Helper for constructing attachment property""" + return _str_to_dict + def extend(self, iterable: "Iterable") -> None: """Extend KProperty with list of KPropertyItem""" from dsms import KItem diff --git a/dsms/knowledge/properties/authors.py b/dsms/knowledge/properties/authors.py index e969bfe..4dafa98 100644 --- a/dsms/knowledge/properties/authors.py +++ b/dsms/knowledge/properties/authors.py @@ -25,3 +25,8 @@ class AuthorsProperty(KProperty): def k_property_item(cls) -> "Callable": """Author data model""" return Author + + # OVERRIDE + @property + def k_property_helper(cls) -> None: + """Not defined for Authors""" diff --git a/dsms/knowledge/properties/base.py b/dsms/knowledge/properties/base.py index 16518b7..b65f7be 100644 --- a/dsms/knowledge/properties/base.py +++ b/dsms/knowledge/properties/base.py @@ -51,11 +51,15 @@ def __repr__(self) -> str: """Pretty print the KProperty""" return str(self) - def __setattr__(self, index: int, item: "Any") -> None: + def __setattr__(self, key: str, item: "Any") -> None: """Add KItem to updated buffer.""" - if self.kitem and self.kitem.id not in self.context.buffers.updated: + if ( + self.kitem + and self.kitem.id not in self.context.buffers.updated + and key not in ["_kitem", "kitem", "id"] + ): self.context.buffers.updated.update({self.id: self.kitem}) - super().__setattr__(index, item) + super().__setattr__(key, item) def __hash__(self) -> int: return hash(str(self)) @@ -98,13 +102,20 @@ class KProperty(list): def __init__(self, *args) -> None: self._kitem: "KItem" = None - self.extend(args) + to_extend = self._get_extendables(args) + self.extend(to_extend) @property @abstractmethod def k_property_item(cls) -> "Callable": """Return the KPropertyItem-class for the KProperty""" + @property + @abstractmethod + def k_property_helper(cls) -> "Optional[Callable]": + """Optional helper for transforming a given + input into the k property item""" + def __str__(self) -> str: """Pretty print the KProperty""" values = ", \n".join(["\t\t" + repr(value) for value in self]) @@ -139,8 +150,7 @@ def __imul__(self, index: int) -> None: self._mark_as_updated() super().__imul__(index) - def extend(self, iterable: "Iterable") -> None: - """Extend KProperty with list of KPropertyItem""" + def _get_extendables(self, iterable: "Iterable") -> "List[KPropertyItem]": from dsms import KItem to_extend = [] @@ -157,6 +167,12 @@ def extend(self, iterable: "Iterable") -> None: else: if not item in self: to_extend.append(item) + return to_extend + + def extend(self, iterable: "Iterable") -> None: + """Extend KProperty with list of KPropertyItem""" + to_extend = self._get_extendables(iterable) + if to_extend: self._mark_as_updated() super().extend(to_extend) @@ -201,14 +217,15 @@ def _check_k_property_item( self, item: "Union[Dict, Any]" ) -> KPropertyItem: """Check the type of the processsed KPropertyItem""" - from dsms import KItem - - if not isinstance(item, (self.k_property_item, dict, KItem, str)): - raise TypeError( - f"""Item `{item}` must be of type {self.k_property_item}, {KItem}, {str} or {dict}, - not `{type(item)}`.""" - ) - if isinstance(item, dict): + if not isinstance(item, BaseModel): + if self.k_property_helper and not isinstance(item, dict): + item = self.k_property_helper(item) + elif not self.k_property_helper and not isinstance(item, dict): + raise TypeError( + f"""No `k_propertyhelper` defined for {type(self)}. + Hence, item `{item}` must be of type {self.k_property_item}, + {BaseModel} or {dict}, not `{type(item)}`.""" + ) item = self.k_property_item(**item) return item diff --git a/dsms/knowledge/properties/contacts.py b/dsms/knowledge/properties/contacts.py index edcd9e5..bb21a8b 100644 --- a/dsms/knowledge/properties/contacts.py +++ b/dsms/knowledge/properties/contacts.py @@ -29,3 +29,8 @@ class ContactsProperty(KProperty): @property def k_property_item(cls) -> "Callable": return ContactInfo + + # OVERRIDE + @property + def k_property_helper(cls) -> None: + """Not defined for Contacts""" diff --git a/dsms/knowledge/properties/external_links.py b/dsms/knowledge/properties/external_links.py index 84505da..081e04e 100644 --- a/dsms/knowledge/properties/external_links.py +++ b/dsms/knowledge/properties/external_links.py @@ -24,3 +24,8 @@ class ExternalLinksProperty(KProperty): @property def k_property_item(cls) -> "Callable": return ExternalLink + + # OVERRIDE + @property + def k_property_helper(cls) -> None: + """Not defined for External links""" diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index 76a527f..295bde0 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -111,6 +111,11 @@ class HDF5Container(KProperty): def k_property_item(cls) -> "Callable": return Column + # OVERRIDE + @property + def k_property_helper(cls) -> None: + """Not defined for HDF5""" + def to_df(self) -> pd.DataFrame: """Return hdf5 as pandas DataFrame""" data = {column.name: column.get() for column in self} diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index 4b8744a..5429207 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -15,6 +15,14 @@ from dsms import KItem +def _linked_kitem_helper(kitem: "KItem"): + from dsms import KItem + + if not isinstance(kitem, KItem): + raise TypeError(f"{kitem} is not of type {KItem}") + return {"id": kitem.id} + + class LinkedKItem(KPropertyItem): """Data model of a linked KItem""" @@ -58,6 +66,12 @@ class LinkedKItemsProperty(KProperty): def k_property_item(cls) -> "Callable": return LinkedKItem + # OVERRIDE + @property + def k_property_helper(cls) -> "Callable": + """Linked KItem helper function""" + return _linked_kitem_helper + def get(self, kitem_id: "Union[str, UUID]") -> "KItem": """Get the kitem with a certain id which is linked to the source KItem.""" if not str(kitem_id) in [str(item.id) for item in self]: diff --git a/dsms/knowledge/properties/user_groups.py b/dsms/knowledge/properties/user_groups.py index 1ef690b..f43cb64 100644 --- a/dsms/knowledge/properties/user_groups.py +++ b/dsms/knowledge/properties/user_groups.py @@ -24,3 +24,7 @@ class UserGroupsProperty(KProperty): def k_property_item(cls) -> "Callable": """UserGroup data model""" return UserGroup + + @property + def k_property_helper(cls) -> None: + """Not defined for User groups""" diff --git a/dsms/knowledge/properties/utils.py b/dsms/knowledge/properties/utils.py new file mode 100644 index 0000000..7d68677 --- /dev/null +++ b/dsms/knowledge/properties/utils.py @@ -0,0 +1,10 @@ +"""DSMS KItem Property utils""" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict + + +def _str_to_dict(name: str) -> "Dict[str, str]": + return {"name": name} diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 4032fa7..acd9392 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -115,11 +115,9 @@ def __setattr_property__(self, key, value) -> None: # convert to convertable numeric object value = _create_numerical_dtype(key, value, self.kitem) # mark as updated - if self.kitem: - self.kitem.context.buffers.updated.update( - {self.kitem.id: self.kitem} - ) - if key == "kitem": + if key != "kitem" and self.kitem: + self.kitem.context.buffers.updated.update({self.kitem.id: self.kitem}) + elif key == "kitem": # set kitem for convertable numeric datatype for prop in self.model_dump().values(): if isinstance(prop, NumericalDataType) and not prop.kitem: @@ -229,17 +227,18 @@ def _create_new_kitem(kitem: "KItem") -> None: ) -def _update_kitem(kitem: "KItem") -> Response: +def _update_kitem(new_kitem: "KItem", old_kitem: "KItem") -> Response: """Update a KItem in the remote backend.""" - old_kitem = _get_kitem(kitem.id) - differences = _get_kitems_diffs(old_kitem, kitem) - dumped = kitem.model_dump_json( + differences = _get_kitems_diffs(old_kitem, new_kitem) + dumped = new_kitem.model_dump_json( exclude={ "authors", "annotations", "custom_properties", "linked_kitems", "updated_at", + "rdf_exists", + "in_backend", "avatar_exists", "user_groups", "ktype_id", @@ -255,23 +254,20 @@ def _update_kitem(kitem: "KItem") -> Response: payload = json.loads(dumped) payload.update( external_links={ - link.label: str(link.url) for link in kitem.external_links + link.label: str(link.url) for link in new_kitem.external_links }, **differences, ) - if kitem.custom_properties: - custom_properties = kitem.custom_properties.model_dump() + if new_kitem.custom_properties: + custom_properties = new_kitem.custom_properties.model_dump() payload.update(custom_properties={"content": custom_properties}) response = _perform_request( - f"api/knowledge/kitems/{kitem.id}", "put", json=payload + f"api/knowledge/kitems/{new_kitem.id}", "put", json=payload ) if not response.ok: raise ValueError( - f"KItem with uuid `{kitem.id}` could not be updated in DSMS: {response.text}`" + f"KItem with uuid `{new_kitem.id}` could not be updated in DSMS: {response.text}`" ) - for key, value in _get_kitem(kitem.id).__dict__.items(): - if key != "attachments": - setattr(kitem, key, value) return response @@ -284,16 +280,13 @@ def _delete_kitem(kitem: "KItem") -> None: ) -def _update_attachments(kitem: "KItem") -> None: +def _update_attachments(new_kitem: "KItem", old_kitem: "KItem") -> None: """Update attachments of the KItem.""" - old_kitem = _get_kitem(kitem.id) - differences = _get_attachment_diffs(old_kitem, kitem) + differences = _get_attachment_diffs(old_kitem, new_kitem) for upload in differences["add"]: - _upload_attachments(kitem, upload.name) + _upload_attachments(new_kitem, upload.name) for remove in differences["remove"]: - _delete_attachments(kitem, remove.name) - for key, value in _get_kitem(kitem.id).__dict__.items(): - setattr(kitem, key, value) + _delete_attachments(new_kitem, remove.name) def _upload_attachments(kitem: "KItem", attachment: "str") -> None: @@ -392,31 +385,44 @@ def _commit(buffers: "Buffers") -> None: def _commit_created(buffer: "Dict[str, KItem]") -> dict: """Commit the buffer for the `created` buffers""" for kitem in buffer.values(): - exists = _kitem_exists(kitem) - if not exists: - _create_new_kitem(kitem) + _create_new_kitem(kitem) def _commit_updated(buffer: "Dict[str, KItem]") -> None: """Commit the buffer for the `updated` buffers""" - for kitem in buffer.values(): - if _kitem_exists(kitem): - if isinstance(kitem.hdf5, pd.DataFrame): - _update_hdf5(kitem.id, kitem.hdf5) - elif isinstance(kitem.hdf5, type(None)) and _inspect_hdf5( - kitem.id + for new_kitem in buffer.values(): + old_kitem = _get_kitem(new_kitem.id) + if old_kitem: + if isinstance(new_kitem.hdf5, pd.DataFrame): + _update_hdf5(new_kitem.id, new_kitem.hdf5) + elif isinstance(new_kitem.hdf5, type(None)) and _inspect_hdf5( + new_kitem.id ): - _delete_hdf5(kitem.id) - _update_kitem(kitem) - _update_attachments(kitem) + _delete_hdf5(new_kitem.id) + _update_kitem(new_kitem, old_kitem) + _update_attachments(new_kitem, old_kitem) + for key, value in _get_kitem(new_kitem.id).__dict__.items(): + setattr(new_kitem, key, value) def _commit_deleted(buffer: "Dict[str, KItem]") -> None: """Commit the buffer for the `deleted` buffers""" for kitem in buffer.values(): - if _kitem_exists(kitem): - _delete_hdf5(kitem.id) - _delete_kitem(kitem) + _delete_hdf5(kitem.id) + _delete_kitem(kitem) + + +def _split_iri(iri: str) -> List[str]: + if "#" in iri: + namspace, name = iri.rsplit("#", 1) + else: + namspace, name = iri.rsplit("/", 1) + return namspace, name + + +def _make_annotation_schema(iri: str) -> Dict[str, Any]: + namespace, name = _split_iri(iri) + return {"namespace": namespace, "name": name, "iri": iri} def _search( @@ -427,12 +433,12 @@ def _search( allow_fuzzy: "Optional[bool]" = True, ) -> "List[SearchResult]": """Search for KItems in the remote backend""" - from dsms import KItem # isort:skip + from dsms import KItem payload = { "search_term": query or "", "ktypes": [ktype.value for ktype in ktypes], - "annotations": annotations, + "annotations": [_make_annotation_schema(iri) for iri in annotations], "limit": limit, } response = _perform_request( @@ -472,11 +478,14 @@ def _slug_is_available(ktype_id: Union[str, UUID], value: str) -> bool: response = _perform_request( f"api/knowledge/kitems/{ktype_id}/{value}", "head" ) + if response.status_code == 401: + raise RuntimeError("The access token has expired") return response.status_code == 404 def _get_hdf5_column(kitem_id: str, column_id: int) -> List[Any]: """Download the column of a hdf5 container of a certain kitem""" + response = _perform_request( f"api/knowledge/data_api/{kitem_id}/column-{column_id}", "get" ) diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 76ec48e..06abf5e 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -18,12 +18,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ - "import os\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -36,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -75,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -86,7 +84,9 @@ "KTypes.Expert\n", "KTypes.App\n", "KTypes.DatasetCatalog\n", - "KTypes.Dataset\n" + "KTypes.Dataset\n", + "KTypes.Specimen\n", + "KTypes.Testingmachine\n" ] } ], @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -121,11 +121,11 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 6630c022-5c65-4fe4-a109-58828aedf145, \n", + "\tid = 013d805f-d7c3-42f0-9b07-ccf10adf3d0f, \n", "\n", "\tktype_id = KTypes.Dataset, \n", "\n", - "\tslug = foo123-6630c022, \n", + "\tslug = foo123-013d805f, \n", "\n", "\tannotations = [], \n", "\n", @@ -157,11 +157,13 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None\n", + "\thdf5 = None, \n", + "\n", + "\trdf_exists = False\n", ")" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -185,16 +187,16 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'https://stahldigital.materials-data.space/dataset/foo123-6630c022'" + "'https://stahldigital.materials-data.space/knowledge/dataset/foo123-013d805f'" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -213,7 +215,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -223,11 +225,11 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 6630c022-5c65-4fe4-a109-58828aedf145, \n", + "\tid = 013d805f-d7c3-42f0-9b07-ccf10adf3d0f, \n", "\n", "\tktype_id = dataset, \n", "\n", - "\tslug = foo123-6630c022, \n", + "\tslug = foo123-013d805f, \n", "\n", "\tannotations = [], \n", "\n", @@ -247,9 +249,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-04-17 23:30:44.313881, \n", + "\tcreated_at = 2024-04-23 20:30:55.386978, \n", "\n", - "\tupdated_at = 2024-04-17 23:30:44.313881, \n", + "\tupdated_at = 2024-04-23 20:30:55.386978, \n", "\n", "\texternal_links = [], \n", "\n", @@ -263,11 +265,13 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None\n", + "\thdf5 = None, \n", + "\n", + "\trdf_exists = False\n", ")" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -297,28 +301,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ - "# specify the path to any arbitrary file to be uploaded\n", - "file = os.path.join(\"..\", \"README.md\")\n", - "\n", "item.name = \"foobar\"\n", "item.custom_properties.foobar = \"foobar\"\n", - "item.attachments.append({\"name\": file})\n", - "item.annotations.append(\n", - " {\n", - " \"iri\": \"www.example.org/foo\",\n", - " \"name\": \"example class\",\n", - " \"namespace\": \"www.example.org\",\n", - " }\n", - ")\n", + "item.attachments.append(\"../README.md\")\n", + "item.annotations.append(\"www.example.org/foo\")\n", "item.external_links.append(\n", " {\"url\": \"http://example.org\", \"label\": \"example link\"}\n", ")\n", "item.contacts.append({\"name\": \"foo\", \"email\": \"foo@bar.mail\"})\n", - "item.affiliations.append({\"name\": \"foobar team\"})\n", + "item.affiliations.append(\"foobar team\")\n", "item.user_groups.append({\"name\": \"foogroup\", \"group_id\": \"123\"})" ] }, @@ -331,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -354,7 +349,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -440,7 +435,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -452,7 +447,7 @@ "\t\t}" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -474,7 +469,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -490,7 +485,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -500,11 +495,11 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 6630c022-5c65-4fe4-a109-58828aedf145, \n", + "\tid = 013d805f-d7c3-42f0-9b07-ccf10adf3d0f, \n", "\n", "\tktype_id = dataset, \n", "\n", - "\tslug = foo123-6630c022, \n", + "\tslug = foo123-013d805f, \n", "\n", "\tannotations = [], \n", "\n", @@ -524,9 +519,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-04-17 23:30:44.313881, \n", + "\tcreated_at = 2024-04-23 20:30:55.386978, \n", "\n", - "\tupdated_at = 2024-04-17 23:30:57.022527, \n", + "\tupdated_at = 2024-04-23 20:31:34.485496, \n", "\n", "\texternal_links = [], \n", "\n", @@ -540,11 +535,13 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None\n", + "\thdf5 = None, \n", + "\n", + "\trdf_exists = False\n", ")" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -562,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -578,7 +575,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -594,7 +591,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -623,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -636,13 +633,7 @@ " name=\"foo 2\",\n", " ktype_id=dsms.ktypes.Organization,\n", " linked_kitems=[item],\n", - " annotations=[\n", - " {\n", - " \"iri\": \"www.example.org/foo\",\n", - " \"name\": \"foo\",\n", - " \"namespace\": \"www.example.org\",\n", - " }\n", - " ],\n", + " annotations=[\"www.example.org/foo\"]\n", ")\n", "item3 = KItem(\n", " name=\"foo 3\", \n", @@ -651,13 +642,7 @@ "item4 = KItem(\n", " name=\"foo 4\",\n", " ktype_id=dsms.ktypes.Organization,\n", - " annotations=[\n", - " {\n", - " \"iri\": \"www.example.org/bar\",\n", - " \"name\": \"bar\",\n", - " \"namespace\": \"https://www.example.org\",\n", - " }\n", - " ],\n", + " annotations=[\"www.example.org/bar\"],\n", ")\n", "\n", "dsms.commit()" @@ -672,7 +657,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -680,71 +665,6 @@ "text/plain": [ "[SearchResult(hit=KItem(\n", " \n", - " \tname = csv_test_bulge, \n", - " \n", - " \tid = 2539dfde-abc9-4fdd-9204-026252ee952c, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csvtestbulge, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", - " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43 1.txt\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", - " \t\t\tsource_id: 2539dfde-abc9-4fdd-9204-026252ee952c\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-02-15 13:05:49.418303, \n", - " \n", - " \tupdated_at = 2024-02-15 13:05:49.418303, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [\n", - " \t\t{\n", - " \t\t\tkitem_app_id: 6,\n", - " \t\t\texecutable: csv_tensile_test.ipynb,\n", - " \t\t\ttitle: csv_notebook,\n", - " \t\t\tdescription: Csv_notebook,\n", - " \t\t\ttags: None,\n", - " \t\t\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv', '.txt']}\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", " \tname = EBSD_HX340LAD, \n", " \n", " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", @@ -789,7 +709,9 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", @@ -811,12 +733,7 @@ " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", - " \t\t\tsource_id: 0f995978-9932-436a-9d76-961463457ed2\n", - " \t\t}\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", @@ -844,7 +761,9 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", @@ -888,35 +807,35 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = ggggg, \n", + " \tname = foo 1, \n", " \n", - " \tid = 970f7396-a71a-49bd-b16e-58e412fe6c54, \n", + " \tid = b712d842-6f68-4f5b-9eaa-b15b495e2ac5, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = ggggg, \n", + " \tslug = foo1-b712d842, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/material#Metallic,\n", - " \t\t\tname: Metallic,\n", - " \t\t\tnamespace: material\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: fe756f5e-aa6e-40a2-a767-570b14384e67\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -924,9 +843,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-19 14:20:27.540398, \n", + " \tcreated_at = 2024-04-23 20:32:34.212108, \n", " \n", - " \tupdated_at = 2024-03-19 14:20:27.540398, \n", + " \tupdated_at = 2024-04-23 20:32:34.212108, \n", " \n", " \texternal_links = [], \n", " \n", @@ -938,7 +857,9 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", @@ -986,28 +907,25 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", " \tname = foo 1, \n", " \n", - " \tid = 8e3861e6-3479-411d-a460-addb91459da4, \n", + " \tid = 67106c23-325d-4f7f-b928-ab8dc7429942, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-8e3861e6, \n", + " \tslug = foo1-67106c23, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89,\n", - " \t\t\tsource_id: 8e3861e6-3479-411d-a460-addb91459da4\n", - " \t\t}\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", @@ -1021,9 +939,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:25.540343, \n", + " \tcreated_at = 2024-04-23 20:10:05.940498, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:25.540343, \n", + " \tupdated_at = 2024-04-23 20:10:05.940498, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1033,12 +951,11 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: 8e3861e6-3479-411d-a460-addb91459da4, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", @@ -1086,7 +1003,9 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", @@ -1130,7 +1049,9 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", @@ -1174,11 +1095,13 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False)]" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1196,7 +1119,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -1224,16 +1147,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: 2539dfde-abc9-4fdd-9204-026252ee952c,\n", - " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b23df795-9441-4a0d-993a-aaeee53c4164,\n", - " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 0f995978-9932-436a-9d76-961463457ed2,\n", - " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t\tid: 3352c236-b925-4f3a-86d3-87145f27834b\n", " \t\t}\n", " \t], \n", " \n", @@ -1263,41 +1177,87 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_test_bulge, \n", + " \tname = EBSD_HX340LAD, \n", " \n", - " \tid = 2539dfde-abc9-4fdd-9204-026252ee952c, \n", + " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = csvtestbulge, \n", + " \tslug = ebsdhx340lad, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [\n", " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43 1.txt\n", - " \t\t}, \n", + " \t\t\tname: 11533_FS_Scan1_830x830_100x.ang\n", + " \t\t}\n", + " \t], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [\n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-02-20 14:41:23.907652, \n", + " \n", + " \tupdated_at = 2024-02-20 14:41:23.907652, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", + " \n", + " \tname = testtest, \n", + " \n", + " \tid = 0f995978-9932-436a-9d76-961463457ed2, \n", + " \n", + " \tktype_id = dataset-catalog, \n", + " \n", + " \tslug = testtest, \n", + " \n", + " \tannotations = [\n", " \t\t{\n", - " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", - " \t\t\tsource_id: 2539dfde-abc9-4fdd-9204-026252ee952c\n", + " \t\t\tiri: http://www.oie.eu/ontology/material#SinglePhaseAlloy,\n", + " \t\t\tname: SinglePhaseAlloy,\n", + " \t\t\tnamespace: material\n", " \t\t}\n", " \t], \n", " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: de5e5310-ae4b-4383-b1d5-1fc6cbbc5f2f\n", " \t\t}\n", " \t], \n", " \n", @@ -1305,22 +1265,13 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-15 13:05:49.418303, \n", + " \tcreated_at = 2024-03-14 08:44:46.504818, \n", " \n", - " \tupdated_at = 2024-02-15 13:05:49.418303, \n", + " \tupdated_at = 2024-03-14 08:44:46.504818, \n", " \n", " \texternal_links = [], \n", " \n", - " \tkitem_apps = [\n", - " \t\t{\n", - " \t\t\tkitem_app_id: 6,\n", - " \t\t\texecutable: csv_tensile_test.ipynb,\n", - " \t\t\ttitle: csv_notebook,\n", - " \t\t\tdescription: Csv_notebook,\n", - " \t\t\ttags: None,\n", - " \t\t\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv', '.txt']}\n", - " \t\t}\n", - " \t], \n", + " \tkitem_apps = [], \n", " \n", " \tsummary = None, \n", " \n", @@ -1328,25 +1279,23 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = EBSD_HX340LAD, \n", + " \tname = my_tensiletest, \n", " \n", - " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", + " \tid = e0b09fe1-5425-4053-abc3-86e7e9e49b25, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = ebsdhx340lad, \n", + " \tslug = mytensiletest, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", - " \t\t{\n", - " \t\t\tname: 11533_FS_Scan1_830x830_100x.ang\n", - " \t\t}\n", - " \t], \n", + " \tattachments = [], \n", " \n", " \tlinked_kitems = [], \n", " \n", @@ -1362,9 +1311,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-20 14:41:23.907652, \n", + " \tcreated_at = 2024-03-15 08:41:32.727310, \n", " \n", - " \tupdated_at = 2024-02-20 14:41:23.907652, \n", + " \tupdated_at = 2024-03-15 08:41:32.727310, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1376,32 +1325,27 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = testtest, \n", + " \tname = foo 1, \n", " \n", - " \tid = 0f995978-9932-436a-9d76-961463457ed2, \n", + " \tid = b712d842-6f68-4f5b-9eaa-b15b495e2ac5, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = testtest, \n", + " \tslug = foo1-b712d842, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/material#SinglePhaseAlloy,\n", - " \t\t\tname: SinglePhaseAlloy,\n", - " \t\t\tnamespace: material\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: 6c695c8b-06ec-4df8-b840-d2dc0752e22d,\n", - " \t\t\tsource_id: 0f995978-9932-436a-9d76-961463457ed2\n", + " \t\t\tid: fe756f5e-aa6e-40a2-a767-570b14384e67\n", " \t\t}\n", " \t], \n", " \n", @@ -1409,7 +1353,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: de5e5310-ae4b-4383-b1d5-1fc6cbbc5f2f\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -1417,9 +1361,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-14 08:44:46.504818, \n", + " \tcreated_at = 2024-04-23 20:32:34.212108, \n", " \n", - " \tupdated_at = 2024-03-14 08:44:46.504818, \n", + " \tupdated_at = 2024-04-23 20:32:34.212108, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1431,29 +1375,41 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = my_tensiletest, \n", + " \tname = foo 2, \n", " \n", - " \tid = e0b09fe1-5425-4053-abc3-86e7e9e49b25, \n", + " \tid = fe756f5e-aa6e-40a2-a767-570b14384e67, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", - " \tslug = mytensiletest, \n", + " \tslug = foo2-fe756f5e, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: b712d842-6f68-4f5b-9eaa-b15b495e2ac5\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -1461,9 +1417,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-15 08:41:32.727310, \n", + " \tcreated_at = 2024-04-23 20:32:35.051894, \n", " \n", - " \tupdated_at = 2024-03-15 08:41:32.727310, \n", + " \tupdated_at = 2024-04-23 20:32:35.051894, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1475,25 +1431,21 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = ggggg, \n", + " \tname = foo 3, \n", " \n", - " \tid = 970f7396-a71a-49bd-b16e-58e412fe6c54, \n", + " \tid = 7528f2d0-5c17-4dc2-9cc0-5fb85b49db4d, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", - " \tslug = ggggg, \n", + " \tslug = foo3-7528f2d0, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/material#Metallic,\n", - " \t\t\tname: Metallic,\n", - " \t\t\tnamespace: material\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", @@ -1503,7 +1455,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -1511,9 +1463,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-19 14:20:27.540398, \n", + " \tcreated_at = 2024-04-23 20:32:35.916822, \n", " \n", - " \tupdated_at = 2024-03-19 14:20:27.540398, \n", + " \tupdated_at = 2024-04-23 20:32:35.916822, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1525,33 +1477,37 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_test, \n", + " \tname = foo 4, \n", " \n", - " \tid = 99bbb5d3-add5-42b0-9b75-93ee29b01766, \n", + " \tid = 965450e5-1c80-4794-a3ae-15c1018e4e4d, \n", " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tslug = csvtest, \n", + " \tktype_id = organization, \n", " \n", - " \tannotations = [], \n", + " \tslug = foo4-965450e5, \n", " \n", - " \tattachments = [\n", + " \tannotations = [\n", " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: www.example.org\n", " \t\t}\n", " \t], \n", " \n", + " \tattachments = [], \n", + " \n", " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -1559,9 +1515,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:32:06.073842, \n", + " \tcreated_at = 2024-04-23 20:32:36.764587, \n", " \n", - " \tupdated_at = 2024-04-16 18:32:06.073842, \n", + " \tupdated_at = 2024-04-23 20:32:36.764587, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1573,34 +1529,35 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = csv_test, \n", " \n", - " \tid = 8e3861e6-3479-411d-a460-addb91459da4, \n", + " \tid = 99bbb5d3-add5-42b0-9b75-93ee29b01766, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-8e3861e6, \n", + " \tslug = csvtest, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", + " \tattachments = [\n", " \t\t{\n", - " \t\t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89,\n", - " \t\t\tsource_id: 8e3861e6-3479-411d-a460-addb91459da4\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", " \t\t}\n", " \t], \n", " \n", + " \tlinked_kitems = [], \n", + " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", " \t\t}\n", " \t], \n", " \n", @@ -1608,9 +1565,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:25.540343, \n", + " \tcreated_at = 2024-04-16 18:32:06.073842, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:25.540343, \n", + " \tupdated_at = 2024-04-16 18:32:06.073842, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1620,22 +1577,21 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: 8e3861e6-3479-411d-a460-addb91459da4, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 3, \n", + " \tname = foo 1, \n", " \n", - " \tid = 17f703f5-cd63-4575-a440-112b70db774d, \n", + " \tid = 67106c23-325d-4f7f-b928-ab8dc7429942, \n", " \n", - " \tktype_id = organization, \n", + " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo3-17f703f5, \n", + " \tslug = foo1-67106c23, \n", " \n", " \tannotations = [], \n", " \n", @@ -1655,9 +1611,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:27.691193, \n", + " \tcreated_at = 2024-04-23 20:10:05.940498, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:27.691193, \n", + " \tupdated_at = 2024-04-23 20:10:05.940498, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1667,30 +1623,25 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: 17f703f5-cd63-4575-a440-112b70db774d, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", + " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = test, \n", + " \tname = new1, \n", " \n", - " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", + " \tid = 47950cae-fd39-430c-8bb5-3f894a032d77, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = test, \n", + " \tslug = new1, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", - " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", - " \t\t}\n", - " \t], \n", + " \tattachments = [], \n", " \n", " \tlinked_kitems = [], \n", " \n", @@ -1706,9 +1657,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:33:54.428378, \n", + " \tcreated_at = 2024-04-16 18:36:49.861912, \n", " \n", - " \tupdated_at = 2024-04-16 18:33:54.428378, \n", + " \tupdated_at = 2024-04-16 18:36:49.861912, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1720,11 +1671,13 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", - " ), fuzzy=False)]" + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=245.71)]" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1742,7 +1695,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -1752,11 +1705,11 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 8e3861e6-3479-411d-a460-addb91459da4, \n", + " \tid = b712d842-6f68-4f5b-9eaa-b15b495e2ac5, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-8e3861e6, \n", + " \tslug = foo1-b712d842, \n", " \n", " \tannotations = [], \n", " \n", @@ -1764,8 +1717,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89,\n", - " \t\t\tsource_id: 8e3861e6-3479-411d-a460-addb91459da4\n", + " \t\t\tid: fe756f5e-aa6e-40a2-a767-570b14384e67\n", " \t\t}\n", " \t], \n", " \n", @@ -1781,9 +1733,55 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:25.540343, \n", + " \tcreated_at = 2024-04-23 20:32:34.212108, \n", + " \n", + " \tupdated_at = 2024-04-23 20:32:34.212108, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", + " \n", + " \tname = foo 1, \n", + " \n", + " \tid = 67106c23-325d-4f7f-b928-ab8dc7429942, \n", + " \n", + " \tktype_id = dataset-catalog, \n", + " \n", + " \tslug = foo1-67106c23, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-04-23 20:10:05.940498, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:25.540343, \n", + " \tupdated_at = 2024-04-23 20:10:05.940498, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1793,16 +1791,15 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: 8e3861e6-3479-411d-a460-addb91459da4, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", + " \trdf_exists = False\n", " ), fuzzy=False)]" ] }, - "execution_count": 20, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1820,7 +1817,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -1828,6 +1825,62 @@ "text/plain": [ "[SearchResult(hit=KItem(\n", " \n", + " \tname = foo 2, \n", + " \n", + " \tid = fe756f5e-aa6e-40a2-a767-570b14384e67, \n", + " \n", + " \tktype_id = organization, \n", + " \n", + " \tslug = foo2-fe756f5e, \n", + " \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}\n", + " \t], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: b712d842-6f68-4f5b-9eaa-b15b495e2ac5\n", + " \t\t}\n", + " \t], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\t{\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t}\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-04-23 20:32:35.051894, \n", + " \n", + " \tupdated_at = 2024-04-23 20:32:35.051894, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", + " \n", " \tname = Fraunhofer, \n", " \n", " \tid = 6c695c8b-06ec-4df8-b840-d2dc0752e22d, \n", @@ -1848,16 +1901,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: 2539dfde-abc9-4fdd-9204-026252ee952c,\n", - " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b23df795-9441-4a0d-993a-aaeee53c4164,\n", - " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 0f995978-9932-436a-9d76-961463457ed2,\n", - " \t\t\tsource_id: 6c695c8b-06ec-4df8-b840-d2dc0752e22d\n", + " \t\t\tid: 3352c236-b925-4f3a-86d3-87145f27834b\n", " \t\t}\n", " \t], \n", " \n", @@ -1887,17 +1931,19 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", - " ), fuzzy=False),\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=237.27),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 3, \n", + " \tname = foo 2, \n", " \n", - " \tid = 17f703f5-cd63-4575-a440-112b70db774d, \n", + " \tid = 8fb6f362-f314-4fd3-b07e-78c615d03476, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo3-17f703f5, \n", + " \tslug = foo2-8fb6f362, \n", " \n", " \tannotations = [], \n", " \n", @@ -1917,9 +1963,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:27.691193, \n", + " \tcreated_at = 2024-04-23 20:10:07.488864, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:27.691193, \n", + " \tupdated_at = 2024-04-23 20:10:07.488864, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1929,39 +1975,27 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: 17f703f5-cd63-4575-a440-112b70db774d, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", - " ), fuzzy=False),\n", + " \trdf_exists = False\n", + " ), fuzzy=283.71),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 2, \n", + " \tname = foo 3, \n", " \n", - " \tid = dd54b41e-99fb-428e-a1f3-4812d0207e89, \n", + " \tid = 7528f2d0-5c17-4dc2-9cc0-5fb85b49db4d, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo2-dd54b41e, \n", + " \tslug = foo3-7528f2d0, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 8e3861e6-3479-411d-a460-addb91459da4,\n", - " \t\t\tsource_id: dd54b41e-99fb-428e-a1f3-4812d0207e89\n", - " \t\t}\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", @@ -1975,9 +2009,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:26.613092, \n", + " \tcreated_at = 2024-04-23 20:32:35.916822, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:26.613092, \n", + " \tupdated_at = 2024-04-23 20:32:35.916822, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1987,30 +2021,23 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: dd54b41e-99fb-428e-a1f3-4812d0207e89, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", " \n", - " \thdf5 = None\n", - " ), fuzzy=False),\n", + " \thdf5 = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=284.02),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 4, \n", + " \tname = foo 3, \n", " \n", - " \tid = 4437eab7-2fa3-4338-a617-0d62e50cdd1c, \n", + " \tid = 6a722ad8-ca40-4f5f-bb06-368999c50d5c, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo4-4437eab7, \n", + " \tslug = foo3-6a722ad8, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/bar,\n", - " \t\t\tname: bar,\n", - " \t\t\tnamespace: https://www.example.org\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", @@ -2028,9 +2055,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-17 23:31:29.090119, \n", + " \tcreated_at = 2024-04-23 20:10:10.073601, \n", " \n", - " \tupdated_at = 2024-04-17 23:31:29.090119, \n", + " \tupdated_at = 2024-04-23 20:10:10.073601, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2040,16 +2067,15 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = {\n", - " \t\tid: 4437eab7-2fa3-4338-a617-0d62e50cdd1c, \n", - " \t\tcontent: {}\n", - " \t}, \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", - " ), fuzzy=False)]" + " \trdf_exists = False\n", + " ), fuzzy=284.02)]" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -2069,7 +2095,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -2097,7 +2123,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -2105,17 +2131,43 @@ "text/plain": [ "[App(filename='csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='csv_bulgetest'),\n", " App(filename='csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='csv_tensile_test'),\n", + " App(filename='csv_tensile_test_f2-Copy1/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='csv_tensile_test_f2-Copy1'),\n", " App(filename='csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='csv_tensile_test_f2'),\n", + " App(filename='csv_upload_test/data2rdf/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='csv_upload_test/data2rdf'),\n", " App(filename='excel_component_test/excel_component_test.ipynb', basename='excel_component_test.ipynb', folder='excel_component_test'),\n", " App(filename='excel_nakajima_test/excel_nakajima_test.ipynb', basename='excel_nakajima_test.ipynb', folder='excel_nakajima_test'),\n", " App(filename='excel_notch_test/excel_notch_tensile_test.ipynb', basename='excel_notch_tensile_test.ipynb', folder='excel_notch_test'),\n", " App(filename='excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='excel_tensile_test'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/material_card_export.ipynb', basename='material_card_export.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification_voila_version.ipynb', basename='tensiletest_parameter_identification_voila_version.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/data2rdf/csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='stahldigital_workshop_2024_base_folder/data2rdf/csv_tensile_test'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/data2rdf/excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='stahldigital_workshop_2024_base_folder/data2rdf/excel_tensile_test'),\n", " App(filename='tensiletest-parameter-identification/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='tensiletest-parameter-identification'),\n", " App(filename='tensiletest-parameter-identification/tensiletest_parameter_identification_voila.ipynb', basename='tensiletest_parameter_identification_voila.ipynb', folder='tensiletest-parameter-identification'),\n", - " App(filename='tensiletest-parameter-identification/voila_test.ipynb', basename='voila_test.ipynb', folder='tensiletest-parameter-identification')]" + " App(filename='tensiletest-parameter-identification/voila_test.ipynb', basename='voila_test.ipynb', folder='tensiletest-parameter-identification'),\n", + " App(filename='test/Modul_Datennutzung/Auswertung_Zugversuch/material_card_export.ipynb', basename='material_card_export.ipynb', folder='test/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='test/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='test/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='test/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test1/Modul_Datennutzung/Auswertung_Zugversuch/material_card_export.ipynb', basename='material_card_export.ipynb', folder='test1/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='test1/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='test1/Modul_Datennutzung/Auswertung_Zugversuch'),\n", + " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps')]" ] }, - "execution_count": 23, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -2140,7 +2192,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -2149,23 +2201,23 @@ "text": [ "Column-wise:\n", "column: a ,\n", - " data: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0]\n", + " data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]\n", "column: b ,\n", - " data: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0, 100.0]\n", + " data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]\n", "\n", "As data frame:\n", - " a b\n", - "0 0.0 1.0\n", - "1 1.0 2.0\n", - "2 2.0 3.0\n", - "3 3.0 4.0\n", - "4 4.0 5.0\n", - ".. ... ...\n", - "95 95.0 96.0\n", - "96 96.0 97.0\n", - "97 97.0 98.0\n", - "98 98.0 99.0\n", - "99 99.0 100.0\n", + " a b\n", + "0 0 1\n", + "1 1 2\n", + "2 2 3\n", + "3 3 4\n", + "4 4 5\n", + ".. .. ...\n", + "95 95 96\n", + "96 96 97\n", + "97 97 98\n", + "98 98 99\n", + "99 99 100\n", "\n", "[100 rows x 2 columns]\n" ] @@ -2175,7 +2227,7 @@ "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", "\n", "\n", - "item = KItem(name=\"testdata123\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", + "item = KItem(name=\"testdata1234\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", "dsms.commit()\n", "\n", "print(\"Column-wise:\")\n", @@ -2194,7 +2246,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -2203,7 +2255,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ From 405caf9ab6fa27c81507d8a110674f8a7ec7d0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 23 Apr 2024 23:04:45 +0200 Subject: [PATCH 050/114] Bump version v1.4.0rc8 -> v1.4.0rc9 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 142d1b9..4b7ecf9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc8 +version = v1.4.0rc9 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 7bd65de4b5688cfec9923e256f1fba569e20cd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 24 Apr 2024 01:53:07 +0200 Subject: [PATCH 051/114] further speedup committing --- dsms/core/configuration.py | 8 +- dsms/knowledge/kitem.py | 6 +- dsms/knowledge/properties/hdf5.py | 8 +- dsms/knowledge/utils.py | 58 +-- examples/basic_usage.ipynb | 585 ++++++++++++++++++------------ tests/test_utils.py | 5 +- 6 files changed, 410 insertions(+), 260 deletions(-) diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 50892e5..423a435 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -2,7 +2,7 @@ import urllib import warnings -from typing import Callable, List, Optional, Set, Union +from typing import Callable, Optional, Set, Union import requests from pydantic import AnyUrl, Field, SecretStr, field_validator @@ -103,12 +103,6 @@ class Configuration(BaseSettings): the units of a HDF5 column/ custom property of a KItem.""", ) - filter_bad_hdf5_types: Optional[List[Callable]] = Field( - [str, type(None)], - description="""A list of classes which should be filtered out - from a column of an hdf5 when fetched""", - ) - hide_properties: Set[Union[str, None]] = Field( set(), description="Properties to hide while printing, e.g {'external_links'}", diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 27a4476..906a7dd 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -419,8 +419,8 @@ def validate_ktype(cls, value: KType, info: ValidationInfo) -> KType: def validate_in_backend(cls, value: bool, info: ValidationInfo) -> bool: """Checks whether the kitem already exists""" kitem_id = info.data["id"] - if _kitem_exists(kitem_id): - value = True + if not value: + value = _kitem_exists(kitem_id) return value @field_validator("slug") @@ -432,6 +432,8 @@ def validate_slug(cls, value: str, info: ValidationInfo) -> str: ktype_id = info.data["ktype_id"] kitem_id = info.data.get("id") kitem_exists = info.data.get("in_backend") + if not isinstance(kitem_exists, bool): + kitem_exists = cls.in_backend if not isinstance(ktype_id, str): ktype = ktype_id.value diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index 295bde0..69a7902 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -2,12 +2,11 @@ import logging from typing import TYPE_CHECKING -import numpy as np import pandas as pd from pydantic import Field from dsms.knowledge.properties.base import KProperty, KPropertyItem -from dsms.knowledge.utils import _get_hdf5_column +from dsms.knowledge.utils import _get_hdf5_column, _is_number from dsms.knowledge.semantics.units import ( # isort:skip get_conversion_factor, @@ -100,7 +99,10 @@ def convert_to( input_str, unit_symbol_or_iri, decimals=decimals ) data = self.get() - return list(np.array(data) * factor) + return [ + number * factor if _is_number(number) else number + for number in data + ] class HDF5Container(KProperty): diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index acd9392..52391dd 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -194,7 +194,9 @@ def _kitem_exists(kitem: Union[Any, str, UUID]) -> bool: return response.ok -def _get_kitem(uuid: Union[str, UUID]) -> "KItem": +def _get_kitem( + uuid: Union[str, UUID], as_json=False +) -> "Union[KItem, Dict[str, Any]]": """Get the KItem for a instance with a certain ID from remote backend""" from dsms import Context, KItem @@ -209,7 +211,12 @@ def _get_kitem(uuid: Union[str, UUID]) -> "KItem": f"""An error occured fetching the KItem with uuid `{uuid}`: `{response.text}`""" ) - return KItem(**response.json()) + payload = response.json() + if as_json: + response = payload + else: + response = KItem(**payload) + return response def _create_new_kitem(kitem: "KItem") -> None: @@ -227,9 +234,12 @@ def _create_new_kitem(kitem: "KItem") -> None: ) -def _update_kitem(new_kitem: "KItem", old_kitem: "KItem") -> Response: +def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: """Update a KItem in the remote backend.""" - differences = _get_kitems_diffs(old_kitem, new_kitem) + to_compare = new_kitem.model_dump( + include={"annotations", "linked_kitems", "user_groups", "kitem_apps"} + ) + differences = _get_kitems_diffs(old_kitem, to_compare) dumped = new_kitem.model_dump_json( exclude={ "authors", @@ -280,13 +290,15 @@ def _delete_kitem(kitem: "KItem") -> None: ) -def _update_attachments(new_kitem: "KItem", old_kitem: "KItem") -> None: +def _update_attachments( + new_kitem: "KItem", old_kitem: "Dict[str, Any]" +) -> None: """Update attachments of the KItem.""" differences = _get_attachment_diffs(old_kitem, new_kitem) for upload in differences["add"]: - _upload_attachments(new_kitem, upload.name) + _upload_attachments(new_kitem, upload) for remove in differences["remove"]: - _delete_attachments(new_kitem, remove.name) + _delete_attachments(new_kitem, remove) def _upload_attachments(kitem: "KItem", attachment: "str") -> None: @@ -331,23 +343,25 @@ def _get_attachment(kitem_id: "KItem", file_name: str) -> str: return response.text -def _get_attachment_diffs(kitem_old: "KItem", kitem_new: "KItem"): +def _get_attachment_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): """Check which attachments should be removed and which should be added.""" return { "remove": [ attachment - for name, attachment in kitem_old.attachments.by_name.items() - if name not in kitem_new.attachments.by_name + for attachment in kitem_old.get("attachments") + if attachment not in kitem_new.attachments.by_name ], "add": [ - attachment + attachment.name for name, attachment in kitem_new.attachments.by_name.items() - if name not in kitem_old.attachments.by_name + if name not in kitem_old.get("attachments") ], } -def _get_kitems_diffs(kitem_old: "KItem", kitem_new: "KItem"): +def _get_kitems_diffs( + kitem_old: "Dict[str, Any]", kitem_new: "Dict[str, Any]" +): """Get the differences in the attributes between two kitems""" differences = {} attributes = [ @@ -359,17 +373,13 @@ def _get_kitems_diffs(kitem_old: "KItem", kitem_new: "KItem"): for name, terms in attributes: to_add_name = terms[0] + "_to_" + terms[1] to_remove_name = terms[0] + "_to_" + terms[2] - old_attr = getattr(kitem_old, name) - new_attr = getattr(kitem_new, name) + old_attr = kitem_old.get(name) + new_attr = kitem_new.get(name) differences[to_add_name] = [ - json.loads(attr.model_dump_json()) - for attr in new_attr - if new_attr not in old_attr + attr for attr in new_attr if new_attr not in old_attr ] differences[to_remove_name] = [ - json.loads(attr.model_dump_json()) - for attr in old_attr - if old_attr not in new_attr + attr for attr in old_attr if old_attr not in new_attr ] return differences @@ -391,17 +401,19 @@ def _commit_created(buffer: "Dict[str, KItem]") -> dict: def _commit_updated(buffer: "Dict[str, KItem]") -> None: """Commit the buffer for the `updated` buffers""" for new_kitem in buffer.values(): - old_kitem = _get_kitem(new_kitem.id) + old_kitem = _get_kitem(new_kitem.id, as_json=True) if old_kitem: if isinstance(new_kitem.hdf5, pd.DataFrame): _update_hdf5(new_kitem.id, new_kitem.hdf5) + new_kitem.hdf5 = _inspect_hdf5(new_kitem.id) elif isinstance(new_kitem.hdf5, type(None)) and _inspect_hdf5( new_kitem.id ): _delete_hdf5(new_kitem.id) _update_kitem(new_kitem, old_kitem) _update_attachments(new_kitem, old_kitem) - for key, value in _get_kitem(new_kitem.id).__dict__.items(): + new_kitem.in_backend = True + for key, value in _get_kitem(new_kitem.id, as_json=True).items(): setattr(new_kitem, key, value) diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 06abf5e..1dae37e 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -73,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -121,11 +121,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 013d805f-d7c3-42f0-9b07-ccf10adf3d0f, \n", + "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", "\n", "\tktype_id = KTypes.Dataset, \n", "\n", - "\tslug = foo123-013d805f, \n", + "\tin_backend = False, \n", + "\n", + "\tslug = foo123-f99e1f7d, \n", "\n", "\tannotations = [], \n", "\n", @@ -163,7 +165,7 @@ ")" ] }, - "execution_count": 4, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -187,16 +189,16 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'https://stahldigital.materials-data.space/knowledge/dataset/foo123-013d805f'" + "'https://stahldigital.materials-data.space/knowledge/dataset/foo123-f99e1f7d'" ] }, - "execution_count": 5, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -215,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -225,11 +227,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 013d805f-d7c3-42f0-9b07-ccf10adf3d0f, \n", + "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", "\n", "\tktype_id = dataset, \n", "\n", - "\tslug = foo123-013d805f, \n", + "\tin_backend = True, \n", + "\n", + "\tslug = foo123-f99e1f7d, \n", "\n", "\tannotations = [], \n", "\n", @@ -249,9 +253,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-04-23 20:30:55.386978, \n", + "\tcreated_at = 2024-04-23 23:46:30.402595, \n", "\n", - "\tupdated_at = 2024-04-23 20:30:55.386978, \n", + "\tupdated_at = 2024-04-23 23:46:30.402595, \n", "\n", "\texternal_links = [], \n", "\n", @@ -271,7 +275,7 @@ ")" ] }, - "execution_count": 6, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -301,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -326,13 +330,112 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "dsms.commit()" ] }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = foobar, \n", + "\n", + "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", + "\n", + "\tktype_id = dataset, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = foo123-f99e1f7d, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.example.org/foo,\n", + "\t\t\tname: foo,\n", + "\t\t\tnamespace: www.example.org\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: README.md\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: foobar team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: foo,\n", + "\t\t\temail: foo@bar.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-04-23 23:46:30.402595, \n", + "\n", + "\tupdated_at = 2024-04-23 23:46:33.430291, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: example link,\n", + "\t\t\turl: http://example.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [\n", + "\t\t{\n", + "\t\t\tname: foogroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tfoo: bar\n", + "\t}, \n", + "\n", + "\thdf5 = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -349,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 37, "metadata": {}, "outputs": [ { @@ -435,7 +538,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -447,7 +550,7 @@ "\t\t}" ] }, - "execution_count": 10, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -469,7 +572,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 39, "metadata": {}, "outputs": [], "source": [ @@ -483,9 +586,25 @@ "See the changes:" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Send the changes to the DSMS with the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -495,11 +614,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 013d805f-d7c3-42f0-9b07-ccf10adf3d0f, \n", + "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", "\n", "\tktype_id = dataset, \n", "\n", - "\tslug = foo123-013d805f, \n", + "\tin_backend = True, \n", + "\n", + "\tslug = foo123-f99e1f7d, \n", "\n", "\tannotations = [], \n", "\n", @@ -507,7 +628,11 @@ "\n", "\tlinked_kitems = [], \n", "\n", - "\taffiliations = [], \n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: foobar team\n", + "\t\t}\n", + "\t], \n", "\n", "\tauthors = [\n", "\t\t{\n", @@ -517,13 +642,24 @@ "\n", "\tavatar_exists = False, \n", "\n", - "\tcontacts = [], \n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: foo,\n", + "\t\t\temail: foo@bar.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", "\n", - "\tcreated_at = 2024-04-23 20:30:55.386978, \n", + "\tcreated_at = 2024-04-23 23:46:30.402595, \n", "\n", - "\tupdated_at = 2024-04-23 20:31:34.485496, \n", + "\tupdated_at = 2024-04-23 23:46:33.430291, \n", "\n", - "\texternal_links = [], \n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: example link,\n", + "\t\t\turl: http://example.org/\n", + "\t\t}\n", + "\t], \n", "\n", "\tkitem_apps = [], \n", "\n", @@ -541,7 +677,7 @@ ")" ] }, - "execution_count": 12, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -550,22 +686,6 @@ "item" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Send the changes to the DSMS with the `commit`-method:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -575,7 +695,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 42, "metadata": {}, "outputs": [], "source": [ @@ -591,7 +711,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -620,7 +740,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 44, "metadata": {}, "outputs": [], "source": [ @@ -657,7 +777,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 45, "metadata": {}, "outputs": [ { @@ -671,6 +791,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = ebsdhx340lad, \n", " \n", " \tannotations = [], \n", @@ -721,6 +843,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = testtest, \n", " \n", " \tannotations = [\n", @@ -773,6 +897,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = mytensiletest, \n", " \n", " \tannotations = [], \n", @@ -815,11 +941,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = b712d842-6f68-4f5b-9eaa-b15b495e2ac5, \n", + " \tid = b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-b712d842, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo1-b4bc4f7e, \n", " \n", " \tannotations = [], \n", " \n", @@ -827,7 +955,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: fe756f5e-aa6e-40a2-a767-570b14384e67\n", + " \t\t\tid: 843e49e2-ae33-4534-b0a8-5b93323b62a2\n", " \t\t}\n", " \t], \n", " \n", @@ -843,9 +971,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:34.212108, \n", + " \tcreated_at = 2024-04-23 23:46:45.004142, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:34.212108, \n", + " \tupdated_at = 2024-04-23 23:46:45.004142, \n", " \n", " \texternal_links = [], \n", " \n", @@ -869,6 +997,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = csvtest, \n", " \n", " \tannotations = [], \n", @@ -913,17 +1043,23 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = test, \n", " \n", - " \tid = 67106c23-325d-4f7f-b928-ab8dc7429942, \n", + " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-67106c23, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = test, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [], \n", + " \tattachments = [\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", + " \t], \n", " \n", " \tlinked_kitems = [], \n", " \n", @@ -931,7 +1067,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", " \t\t}\n", " \t], \n", " \n", @@ -939,9 +1075,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:10:05.940498, \n", + " \tcreated_at = 2024-04-16 18:33:54.428378, \n", " \n", - " \tupdated_at = 2024-04-23 20:10:05.940498, \n", + " \tupdated_at = 2024-04-16 18:33:54.428378, \n", " \n", " \texternal_links = [], \n", " \n", @@ -959,21 +1095,19 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = test, \n", + " \tname = INSTABILITAET, \n", " \n", - " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", + " \tid = eff582e8-7117-4b84-81dd-fafd0845bcae, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = test, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = instabilitaet, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", - " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", - " \t\t}\n", - " \t], \n", + " \tattachments = [], \n", " \n", " \tlinked_kitems = [], \n", " \n", @@ -981,7 +1115,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -989,15 +1123,15 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:33:54.428378, \n", + " \tcreated_at = 2024-04-23 21:53:04.128883, \n", " \n", - " \tupdated_at = 2024-04-16 18:33:54.428378, \n", + " \tupdated_at = 2024-04-23 21:53:04.128883, \n", " \n", " \texternal_links = [], \n", " \n", " \tkitem_apps = [], \n", " \n", - " \tsummary = None, \n", + " \tsummary = Summary(text=INSTABILITAET Project at Fraunhofer IWM), \n", " \n", " \tuser_groups = [], \n", " \n", @@ -1015,6 +1149,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = aaaaaa, \n", " \n", " \tannotations = [], \n", @@ -1061,6 +1197,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = new1, \n", " \n", " \tannotations = [], \n", @@ -1101,7 +1239,7 @@ " ), fuzzy=False)]" ] }, - "execution_count": 17, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -1119,7 +1257,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 46, "metadata": {}, "outputs": [ { @@ -1133,6 +1271,8 @@ " \n", " \tktype_id = organization, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = fraunhofer, \n", " \n", " \tannotations = [\n", @@ -1189,6 +1329,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = ebsdhx340lad, \n", " \n", " \tannotations = [], \n", @@ -1239,6 +1381,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = testtest, \n", " \n", " \tannotations = [\n", @@ -1291,6 +1435,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = mytensiletest, \n", " \n", " \tannotations = [], \n", @@ -1333,11 +1479,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = b712d842-6f68-4f5b-9eaa-b15b495e2ac5, \n", + " \tid = b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-b712d842, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo1-b4bc4f7e, \n", " \n", " \tannotations = [], \n", " \n", @@ -1345,7 +1493,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: fe756f5e-aa6e-40a2-a767-570b14384e67\n", + " \t\t\tid: 843e49e2-ae33-4534-b0a8-5b93323b62a2\n", " \t\t}\n", " \t], \n", " \n", @@ -1361,9 +1509,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:34.212108, \n", + " \tcreated_at = 2024-04-23 23:46:45.004142, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:34.212108, \n", + " \tupdated_at = 2024-04-23 23:46:45.004142, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1383,11 +1531,13 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = fe756f5e-aa6e-40a2-a767-570b14384e67, \n", + " \tid = 843e49e2-ae33-4534-b0a8-5b93323b62a2, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo2-fe756f5e, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo2-843e49e2, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1401,7 +1551,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: b712d842-6f68-4f5b-9eaa-b15b495e2ac5\n", + " \t\t\tid: b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175\n", " \t\t}\n", " \t], \n", " \n", @@ -1417,9 +1567,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:35.051894, \n", + " \tcreated_at = 2024-04-23 23:46:45.577428, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:35.051894, \n", + " \tupdated_at = 2024-04-23 23:46:45.577428, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1439,11 +1589,13 @@ " \n", " \tname = foo 3, \n", " \n", - " \tid = 7528f2d0-5c17-4dc2-9cc0-5fb85b49db4d, \n", + " \tid = 33c20a1f-5429-404b-bdc6-3868fd4675e5, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo3-7528f2d0, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo3-33c20a1f, \n", " \n", " \tannotations = [], \n", " \n", @@ -1463,9 +1615,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:35.916822, \n", + " \tcreated_at = 2024-04-23 23:46:46.185321, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:35.916822, \n", + " \tupdated_at = 2024-04-23 23:46:46.185321, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1485,11 +1637,13 @@ " \n", " \tname = foo 4, \n", " \n", - " \tid = 965450e5-1c80-4794-a3ae-15c1018e4e4d, \n", + " \tid = ee5cc269-ef4a-4ff2-b704-60aa66af4b4a, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo4-965450e5, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo4-ee5cc269, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1515,9 +1669,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:36.764587, \n", + " \tcreated_at = 2024-04-23 23:46:46.768925, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:36.764587, \n", + " \tupdated_at = 2024-04-23 23:46:46.768925, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1541,6 +1695,8 @@ " \n", " \tktype_id = dataset-catalog, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = csvtest, \n", " \n", " \tannotations = [], \n", @@ -1585,17 +1741,23 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = test, \n", " \n", - " \tid = 67106c23-325d-4f7f-b928-ab8dc7429942, \n", + " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-67106c23, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = test, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [], \n", + " \tattachments = [\n", + " \t\t{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}\n", + " \t], \n", " \n", " \tlinked_kitems = [], \n", " \n", @@ -1603,7 +1765,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", " \t\t}\n", " \t], \n", " \n", @@ -1611,9 +1773,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:10:05.940498, \n", + " \tcreated_at = 2024-04-16 18:33:54.428378, \n", " \n", - " \tupdated_at = 2024-04-23 20:10:05.940498, \n", + " \tupdated_at = 2024-04-16 18:33:54.428378, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1631,13 +1793,15 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = new1, \n", + " \tname = INSTABILITAET, \n", " \n", - " \tid = 47950cae-fd39-430c-8bb5-3f894a032d77, \n", + " \tid = eff582e8-7117-4b84-81dd-fafd0845bcae, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = new1, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = instabilitaet, \n", " \n", " \tannotations = [], \n", " \n", @@ -1649,7 +1813,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", " \t\t}\n", " \t], \n", " \n", @@ -1657,15 +1821,15 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:36:49.861912, \n", + " \tcreated_at = 2024-04-23 21:53:04.128883, \n", " \n", - " \tupdated_at = 2024-04-16 18:36:49.861912, \n", + " \tupdated_at = 2024-04-23 21:53:04.128883, \n", " \n", " \texternal_links = [], \n", " \n", " \tkitem_apps = [], \n", " \n", - " \tsummary = None, \n", + " \tsummary = Summary(text=INSTABILITAET Project at Fraunhofer IWM), \n", " \n", " \tuser_groups = [], \n", " \n", @@ -1674,58 +1838,30 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=245.71)]" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... or for all of type `DatasetCatalog` with `foo` in the name:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[SearchResult(hit=KItem(\n", + " ), fuzzy=207.91),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = new1, \n", " \n", - " \tid = b712d842-6f68-4f5b-9eaa-b15b495e2ac5, \n", + " \tid = 47950cae-fd39-430c-8bb5-3f894a032d77, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-b712d842, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = new1, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: fe756f5e-aa6e-40a2-a767-570b14384e67\n", - " \t\t}\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", " \t\t}\n", " \t], \n", " \n", @@ -1733,9 +1869,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:34.212108, \n", + " \tcreated_at = 2024-04-16 18:36:49.861912, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:34.212108, \n", + " \tupdated_at = 2024-04-16 18:36:49.861912, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1750,22 +1886,54 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", + " ), fuzzy=245.71)]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... or for all of type `DatasetCatalog` with `foo` in the name:" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SearchResult(hit=KItem(\n", " \n", " \tname = foo 1, \n", " \n", - " \tid = 67106c23-325d-4f7f-b928-ab8dc7429942, \n", + " \tid = b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", - " \tslug = foo1-67106c23, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo1-b4bc4f7e, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 843e49e2-ae33-4534-b0a8-5b93323b62a2\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", @@ -1779,9 +1947,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:10:05.940498, \n", + " \tcreated_at = 2024-04-23 23:46:45.004142, \n", " \n", - " \tupdated_at = 2024-04-23 20:10:05.940498, \n", + " \tupdated_at = 2024-04-23 23:46:45.004142, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1799,7 +1967,7 @@ " ), fuzzy=False)]" ] }, - "execution_count": 19, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -1817,7 +1985,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 48, "metadata": {}, "outputs": [ { @@ -1827,11 +1995,13 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = fe756f5e-aa6e-40a2-a767-570b14384e67, \n", + " \tid = 843e49e2-ae33-4534-b0a8-5b93323b62a2, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo2-fe756f5e, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo2-843e49e2, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1845,7 +2015,7 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: b712d842-6f68-4f5b-9eaa-b15b495e2ac5\n", + " \t\t\tid: b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175\n", " \t\t}\n", " \t], \n", " \n", @@ -1861,9 +2031,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:32:35.051894, \n", + " \tcreated_at = 2024-04-23 23:46:45.577428, \n", " \n", - " \tupdated_at = 2024-04-23 20:32:35.051894, \n", + " \tupdated_at = 2024-04-23 23:46:45.577428, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1887,6 +2057,8 @@ " \n", " \tktype_id = organization, \n", " \n", + " \tin_backend = True, \n", + " \n", " \tslug = fraunhofer, \n", " \n", " \tannotations = [\n", @@ -1937,13 +2109,15 @@ " ), fuzzy=237.27),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 2, \n", + " \tname = foo 3, \n", " \n", - " \tid = 8fb6f362-f314-4fd3-b07e-78c615d03476, \n", + " \tid = 33c20a1f-5429-404b-bdc6-3868fd4675e5, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo2-8fb6f362, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo3-33c20a1f, \n", " \n", " \tannotations = [], \n", " \n", @@ -1963,9 +2137,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:10:07.488864, \n", + " \tcreated_at = 2024-04-23 23:46:46.185321, \n", " \n", - " \tupdated_at = 2024-04-23 20:10:07.488864, \n", + " \tupdated_at = 2024-04-23 23:46:46.185321, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1980,65 +2154,27 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=283.71),\n", + " ), fuzzy=284.02),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 3, \n", + " \tname = foo 4, \n", " \n", - " \tid = 7528f2d0-5c17-4dc2-9cc0-5fb85b49db4d, \n", + " \tid = ee5cc269-ef4a-4ff2-b704-60aa66af4b4a, \n", " \n", " \tktype_id = organization, \n", " \n", - " \tslug = foo3-7528f2d0, \n", + " \tin_backend = True, \n", " \n", - " \tannotations = [], \n", + " \tslug = foo4-ee5cc269, \n", " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", + " \tannotations = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: www.example.org\n", " \t\t}\n", " \t], \n", " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-23 20:32:35.916822, \n", - " \n", - " \tupdated_at = 2024-04-23 20:32:35.916822, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=284.02),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 3, \n", - " \n", - " \tid = 6a722ad8-ca40-4f5f-bb06-368999c50d5c, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tslug = foo3-6a722ad8, \n", - " \n", - " \tannotations = [], \n", - " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [], \n", @@ -2055,9 +2191,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 20:10:10.073601, \n", + " \tcreated_at = 2024-04-23 23:46:46.768925, \n", " \n", - " \tupdated_at = 2024-04-23 20:10:10.073601, \n", + " \tupdated_at = 2024-04-23 23:46:46.768925, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2072,10 +2208,10 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=284.02)]" + " ), fuzzy=296.55)]" ] }, - "execution_count": 20, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -2095,7 +2231,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ @@ -2123,7 +2259,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 50, "metadata": {}, "outputs": [ { @@ -2142,6 +2278,7 @@ " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification_voila_version.ipynb', basename='tensiletest_parameter_identification_voila_version.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", + " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/browse_linked_kitems.ipynb', basename='browse_linked_kitems.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", @@ -2167,7 +2304,7 @@ " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps')]" ] }, - "execution_count": 22, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -2192,7 +2329,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 51, "metadata": {}, "outputs": [ { @@ -2246,7 +2383,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 52, "metadata": {}, "outputs": [], "source": [ @@ -2255,7 +2392,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 53, "metadata": {}, "outputs": [], "source": [ diff --git a/tests/test_utils.py b/tests/test_utils.py index a639ae8..226c4c1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -107,7 +107,10 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): obj.model_dump() for obj in kitem_old.kitem_apps ], } - diffs = _get_kitems_diffs(kitem_old, kitem_new) + to_compare = kitem_new.model_dump( + include={"annotations", "linked_kitems", "user_groups", "kitem_apps"} + ) + diffs = _get_kitems_diffs(kitem_old.model_dump(), to_compare) assert sorted(diffs) == sorted(expected) From 932e16b6bf55f779456775b4c1bdcb2848129783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 24 Apr 2024 01:56:39 +0200 Subject: [PATCH 052/114] Bump version v1.4.0rc9 -> v1.4.0rc10 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4b7ecf9..66fd588 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc9 +version = v1.4.0rc10 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From afc65823f627a66fa6ab3273e772800cad6f3458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 24 Apr 2024 15:12:19 +0200 Subject: [PATCH 053/114] fix attachment by name property --- dsms/knowledge/properties/attachments.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 9a043ab..0f5fd61 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -79,4 +79,8 @@ def insert(self, index: int, item: "Union[Dict, Any]") -> None: @property def by_name(cls) -> "List[str]": "Return list of names of attachments" - return {Path(attachment.name).stem: attachment for attachment in cls} + return { + Path(attachment.name).stem + + Path(attachment.name).suffix: attachment + for attachment in cls + } From 3568a6d4a0ae18048b0c969bbce4df3af7cce6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 24 Apr 2024 15:39:09 +0200 Subject: [PATCH 054/114] Bump version v1.4.0rc10 -> v1.4.0rc11 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 66fd588..acc5f64 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc10 +version = v1.4.0rc11 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From eb65bb5268cd815fda38b7504e395ff7264c0092 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Wed, 1 May 2024 22:25:36 +0000 Subject: [PATCH 055/114] Revert "Real examples for tutorials" This reverts commit 509221a535f4c2ab715b2aee39e33b11050f800c. --- docs/source/tutorials/2_creation.ipynb | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index b49d6fd..8c26bd5 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -77,15 +77,7 @@ "metadata": {}, "source": [ "We can make new KItems by simple class-initiation: (Make Sure existing KItems is not put as input). \n", - "\n", - "Below code makes a simple KItem with following details:\n", - "- **Name** - TestSpecimens\n", - "- **KType** - DatasetCatalog\n", - "- **Custom Properties** \n", - " - ***Shape*** : Cylindrical\n", - " - ***Dimensions*** : 150 x 20 x 40 cm\n", - " - ***Material*** : Metal\n", - " - ***Project ID***: StrengthTesting2024" + "#" ] }, { @@ -118,14 +110,9 @@ ], "source": [ "item = KItem(\n", - " name=\"TestSpecimens\",\n", + " name=\"foo123\",\n", " ktype_id=dsms.ktypes.DatasetCatalog,\n", - " custom_properties={\n", - " \"Shape\": \"Cylindrical\",\n", - " \"Dimensions\": \"150 x 20 x 40 cm\",\n", - " \"Material\": \"Metal\",\n", - " \"Project ID\": \"StrengthTesting2024\"\n", - " }\n", + " custom_properties={\"foo\": \"bar\"},\n", ")\n", "\n", "item" @@ -196,7 +183,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.1.0" + "version": "3.9.19" } }, "nbformat": 4, From 1357e349e1e3ec52690c617cf4d47f90959789a9 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Wed, 1 May 2024 23:04:55 +0000 Subject: [PATCH 056/114] Installation,index and introduction md files updation --- docs/source/index.md | 65 +++++++++++++++++++++++++++++++++++++ docs/source/installation.md | 27 +++++++++++++-- docs/source/introduction.md | 43 ++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 docs/source/index.md diff --git a/docs/source/index.md b/docs/source/index.md new file mode 100644 index 0000000..bac9957 --- /dev/null +++ b/docs/source/index.md @@ -0,0 +1,65 @@ +# DSMS SDK Documentation + +Welcome to the DSMS SDK documentation! + +Here you will find all the information regarding the installation, setup and basic usage of the DSMS python SDK. + + +````{panels} + :body: text-center. + + --- + **Installation** + + Quick-setup of the DSMS-SDK + + ```{link-button} installation.html + :text: Install the SDK! + :classes: btn-outline-primary stretched-link + + --- + **Introduction** + + Overview of the DSMS-SDK. + + ```{link-button} introduction.html + :text: Basics of the SDK + :classes: btn-outline-primary stretched-link + + --- + **Tutorials** + + Get Started with using DSMS-SDK. + + ```{link-button} 1_introduction.html + :text: Jump to the tutorial + :classes: btn-outline-primary stretched-link + + --- + **Documentation** + + ReaxPro use case description. + + ```{link-button} usecases.html + :text: Read the docs + :classes: btn-outline-primary stretched-link + +```` + +Note that these docs are an ongoing effort, so they are likely to change and evolve. +Feel free to report any issues/missing information so we can take a look into it. + +```{toctree} +:hidden: true +:caption: Table of contents +:maxdepth: 1 + +installation +introduction +``` + +```{toctree} +:hidden: true +:caption: Tutorials +:maxdepth: 2 +``` diff --git a/docs/source/installation.md b/docs/source/installation.md index 6a81cc9..ad707f9 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -1,10 +1,33 @@ # Installation and Setup Guide How to install and setup the dsms-python-sdk. +## 1.1. Installation +Before using DSMS-SDK make sure to register your account at your DSMS Instance.You could do this by sending an email to the following contacts: -## 1.1. Installation -Install in your folder (or any folder you like). +- [Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) +- [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) + + +The following are the instances of the DSMS you could choose from: + +- [StahlDigital](https://lnkd.in/gfwe9a36) +- [KupferDigital](https://lnkd.in/g8mvnM3K) +- [DiMAT](https://lnkd.in/g46baB6J) + + +After following the above steps, you could use either of the two ways to install DSMS to your machine. + +#### Method 1 : Via PyPI #### + +To install the [DSMS Python SDK](https://pypi.org/project/dsms-sdk/), you can use the Python Package Index (PyPI). +Copy the below command to install DSMS SDK from PyPI +- `pip install dsms-sdk` + + +#### Method 2 : Cloning Github Repo #### + +Install in your folder (or any folder you like). - `git clone git@github.com:MI-FraunhoferIWM/dsms-python-sdk.git` - `cd dsms-python-sdk` diff --git a/docs/source/introduction.md b/docs/source/introduction.md index 5c097d7..d94a6dd 100644 --- a/docs/source/introduction.md +++ b/docs/source/introduction.md @@ -15,7 +15,7 @@ Just install it on your local system via the pip command line interface: ... and start connecting to your central DSMS instance remotely, e.g. by integrating it into your own Python scripts and packages -Its functionalities: +The SDK functionalities are listed below: 1. Managing Knowledge-Items. 2. Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test. 3. Administrating authorship, contact information and supplementary information. @@ -31,7 +31,17 @@ Its functionalities: ## 1.2. DSMS -## 1.3. KItems and KTypes + +DSMS (acronym for Dataspace Management System) by Fraunhofer is a web-based application that manages heterogeneous data and features semantic and analytical capabilities. + +### 1.2.1. Introduction + +DSMS platform promotes and enables the provenance and catalogization of data through a combintation of classical relational databases and semantic technologies. By enabling the interoperabilty to third party data sources, Fraunhofer IWM demonstrates this though particular use cases in material science and manufacturing in public research projects for industry 4.0. + +![DSMS_Intro](assets/images/dsms_picture_01.jpg) + + +### 1.2.2. KItems and KTypes What is K-type? @@ -43,6 +53,33 @@ K-item stands for knowledge item and represents an individual instance of a k-ty How is it helpful? -This approach streamlines the schematisation, conceptualization and structurisation of data for various domains and applications. Technically speaking, it builds an ideal base for the integration of data and knowledge into large dataspaces. K-items classify through k-types didactically, support the upscaling of information into knowledge graphs by additional semantic annotations - which are usually mapped by ontologists. K-items and k-types embody the concepts of digital twins and data containers by providing a structured and semantic framework for representing real-world materials and processes in the manufacturing industry. +This approach streamlines the schematisation, conceptualization and structurisation of data for various domains and applications. Technically speaking, it builds an ideal base for the integration of data and knowledge into large dataspaces. K-Items classify through K-Types didactically, support the upscaling of information into knowledge graphs by additional semantic annotations - which are usually mapped by ontologists. + +K-Items and K-Types embody the concepts of digital twins and data containers by providing a structured and semantic framework for representing real-world materials and processes in the manufacturing industry. ![DSMS](assets/images/DSMS.jpg) + +### 1.2.3. Kitem Properties + +A Kitem has several properties to enable to handle data effectively. This section briefly describes the properties a Kitem can consist or in simple words the schema of a K-item. + +The schema contains complex types and references, indicating an advanced usage scenario where various objects (like KItems and their properties) are interconnected. It also includes customizations like optional and default values, arrays of references, and conditional formats (e.g., UUID formats). + +For the DSMS, Pydantic has been used extensively. Pydantic is a Python library that leverages type annotations for data validation and settings management. It ensures that data conforms to predefined schemas by validating or serializing it at runtime and thus facilitates strict type checking and data integrity. + +Below given is the details of the schema of a Kitem for better understanding: + +- **AdditionalProperties**: Defines extra, non-standard properties that can be associated with an application. It includes properties like `triggerUponUpload` (a boolean indicating if the app should trigger when a file is uploaded) and `triggerUponUploadFileExtensions` (an array or null defining file extensions for which the upload should be triggered). +- **Affiliation**: Represents the affiliation of a KItem, including identifiers and names. It requires the `name` property. +- **Annotation**: Details annotations related to a KItem, with fields like `iri`, `name`, and `namespace`, all of which are required. +- **App**: Describes an application associated with a KItem, featuring details such as an executable name and additional properties. The `executable` field is mandatory. +- **Attachment**: Defines an attachment, requiring the `name` field and including an identifier. +- **Author**: Specifies an author of a KItem, with a required `userId` indicating the author's user ID. +- **Column**: Represents a column in an HDF5 data frame, requiring both `columnId` and `name`. +- **ContactInfo**: Provides contact information including an email and name, both required. +- **ExternalLink**: Details an external link related to a KItem, requiring `label` and `url`. +- **KItem**: The central model for a Knowledge Item, with comprehensive properties like `affiliations`, `annotations`, `attachments`, and more. Key fields like `name` and `ktype_id` are mandatory. +- **KType**: Describes the type of knowledge contained in a KItem, with necessary fields such as `id`. +- **LinkedKItem**: Models a KItem that is linked to another, requiring identifiers for both the linked item and its source. +- **Summary**: Provides a summary model for KItems, requiring a `text` field. +- **UserGroup**: Specifies user groups related to a KItem, requiring `name` and `groupId`. From 695a26d5ff84e3055e11df00bc221b6c952f8d0c Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 2 May 2024 01:18:33 +0000 Subject: [PATCH 057/114] development until 02nd May: Basic version of the final iteration of Installation, Index and Introduction done --- docs/Makefile | 20 +++ docs/make.bat | 35 ++++++ docs/source/assets/images/DSMS_logo.jpg | Bin 0 -> 91441 bytes docs/source/assets/images/DSMS_logo.png | Bin 0 -> 90468 bytes docs/source/conf.py | 78 ++++++++++++ docs/source/index.md | 54 ++++---- docs/source/introduction.md | 136 ++++++++++++++++++--- docs/source/tutorials/1_introduction.ipynb | 2 +- docs/source/tutorials/2_creation.ipynb | 2 +- 9 files changed, 279 insertions(+), 48 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/assets/images/DSMS_logo.jpg create mode 100644 docs/source/assets/images/DSMS_logo.png create mode 100644 docs/source/conf.py diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/assets/images/DSMS_logo.jpg b/docs/source/assets/images/DSMS_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9925f67d31b63b69a4cea43e628379b13679d7fb GIT binary patch literal 91441 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�$*kTpm4?b<6W-{|T$ys96lJHuf%= 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 q9HzbpPV>Mr=23a&Q!qO?|h7$&hO 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 literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..ab7596e --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,78 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- Project information ----------------------------------------------------- + +project = "DSMS-SDK Documentation" +copyright = "2024, Materials Informatics Team at Fraunhofer IWM" +author = "Materials Informatics, Fraunhofer IWM" +release = "v1.0.0" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. + +extensions = [ + "myst_parser", # Markdown support + "sphinx.ext.autodoc", # Include documentation from docstrings + "sphinx.ext.napoleon", # Google-style docstrings + "sphinx.ext.viewcode", # Add links to highlighted source code + "sphinx.ext.graphviz", # Add support for graphviz + "sphinxcontrib.plantuml", # Add support for plantuml + "sphinx_copybutton", # Add copy button to code blocks + "nbsphinx", # Add support for Jupyter Notebooks + "IPython.sphinxext.ipython_console_highlighting", # Add syntax highlighting for IPython + "sphinx.ext.autosectionlabel", # Add support for autolabeling sections + "sphinx_panels", # Add support for panels + "sphinx_markdown_tables", # Add support for markdown tables + "sphinxcontrib.redoc", # Add support for redoc +] + +master_doc = "index" + +plantuml = "java -jar /path/to/plantuml.jar" +plantuml_output_format = "svg_img" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +templates_path = ["_templates"] +exclude_patterns = ["build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +html_theme = "sphinx_rtd_theme" +html_logo = "assets/images/DSMS_logo.png" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# "style_nav_header_background": "#4472c4", : Blue of DSMS +# "style_nav_header_background": "#109193", : Green of DSMS +html_static_path = ["_static"] +templates_path = ["_templates"] +html_theme_options = { + "style_nav_header_background": "#4472c4", + "use_download_button": True, +} + +nbsphinx_allow_errors = True diff --git a/docs/source/index.md b/docs/source/index.md index bac9957..4e500fd 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -6,43 +6,35 @@ Here you will find all the information regarding the installation, setup and bas ````{panels} - :body: text-center. +:body: text-center. - --- - **Installation** +--- +**Installation** - Quick-setup of the DSMS-SDK +Quick-setup of the DSMS-SDK - ```{link-button} installation.html - :text: Install the SDK! - :classes: btn-outline-primary stretched-link +```{link-button} installation.html +:text: Install the SDK! +:classes: btn-outline-primary stretched-link - --- - **Introduction** +--- +**Introduction** - Overview of the DSMS-SDK. + Overview of the DSMS-SDK. - ```{link-button} introduction.html - :text: Basics of the SDK - :classes: btn-outline-primary stretched-link +```{link-button} introduction.html +:text: Basics of the SDK +:classes: btn-outline-primary stretched-link - --- - **Tutorials** +--- +**Tutorials** - Get Started with using DSMS-SDK. +Get Started with using DSMS-SDK. - ```{link-button} 1_introduction.html - :text: Jump to the tutorial - :classes: btn-outline-primary stretched-link +```{link-button} tutorials/1_introduction.html +:text: Jump to the tutorial +:classes: btn-outline-primary stretched-link - --- - **Documentation** - - ReaxPro use case description. - - ```{link-button} usecases.html - :text: Read the docs - :classes: btn-outline-primary stretched-link ```` @@ -56,10 +48,18 @@ Feel free to report any issues/missing information so we can take a look into it installation introduction +Tutorials ``` ```{toctree} :hidden: true :caption: Tutorials :maxdepth: 2 + +1. Introduction +2. Creation +3. Updation +4. Deletion +5. Search +6. App Availability ``` diff --git a/docs/source/introduction.md b/docs/source/introduction.md index d94a6dd..13ce8b8 100644 --- a/docs/source/introduction.md +++ b/docs/source/introduction.md @@ -3,11 +3,11 @@ In the dynamic world of Material Science, the introduction of our latest development brings a new way of interaction with our Dataspace Management System (DSMS): The DSMS Python SDK. -What is the DSMS SDK? +**What is the DSMS SDK?** SDK stands for Software Development Kit. In our case, it is a Python-based package for the interaction with the DSMS. This means that all fundamental functionalities of the DSMS can be accessed through a Python interface now! -How does the SDK work? +**How does the SDK work?** Just install it on your local system via the pip command line interface: @@ -43,15 +43,15 @@ DSMS platform promotes and enables the provenance and catalogization of data thr ### 1.2.2. KItems and KTypes -What is K-type? +**What is K-type?** K-type stands for knowledge type and categorizes types of knowledge instances and what kind of details are relevant for them (as shown in the attached image). It basically describes a concept and its schema. -What is K-item? +**What is K-item?** K-item stands for knowledge item and represents an individual instance of a k-type following its property schema and functionalities. Knowledge items also capture the concepts of data containers and digital twin. -How is it helpful? +**How is it helpful?** This approach streamlines the schematisation, conceptualization and structurisation of data for various domains and applications. Technically speaking, it builds an ideal base for the integration of data and knowledge into large dataspaces. K-Items classify through K-Types didactically, support the upscaling of information into knowledge graphs by additional semantic annotations - which are usually mapped by ontologists. @@ -69,17 +69,115 @@ For the DSMS, Pydantic has been used extensively. Pydantic is a Python library t Below given is the details of the schema of a Kitem for better understanding: -- **AdditionalProperties**: Defines extra, non-standard properties that can be associated with an application. It includes properties like `triggerUponUpload` (a boolean indicating if the app should trigger when a file is uploaded) and `triggerUponUploadFileExtensions` (an array or null defining file extensions for which the upload should be triggered). -- **Affiliation**: Represents the affiliation of a KItem, including identifiers and names. It requires the `name` property. -- **Annotation**: Details annotations related to a KItem, with fields like `iri`, `name`, and `namespace`, all of which are required. -- **App**: Describes an application associated with a KItem, featuring details such as an executable name and additional properties. The `executable` field is mandatory. -- **Attachment**: Defines an attachment, requiring the `name` field and including an identifier. -- **Author**: Specifies an author of a KItem, with a required `userId` indicating the author's user ID. -- **Column**: Represents a column in an HDF5 data frame, requiring both `columnId` and `name`. -- **ContactInfo**: Provides contact information including an email and name, both required. -- **ExternalLink**: Details an external link related to a KItem, requiring `label` and `url`. -- **KItem**: The central model for a Knowledge Item, with comprehensive properties like `affiliations`, `annotations`, `attachments`, and more. Key fields like `name` and `ktype_id` are mandatory. -- **KType**: Describes the type of knowledge contained in a KItem, with necessary fields such as `id`. -- **LinkedKItem**: Models a KItem that is linked to another, requiring identifiers for both the linked item and its source. -- **Summary**: Provides a summary model for KItems, requiring a `text` field. -- **UserGroup**: Specifies user groups related to a KItem, requiring `name` and `groupId`. +#### KItem Schema Documentation #### + +All the below kitem schema elements have id which is automatically generated which has the type - `UUID`. + +##### 1. KItem ##### + +**Description:** Represents a Knowledge Item within the DSMS system. + +**Properties:** +- **name**: Human-readable name of the KItem. +- **slug**: A unique slug identifier for the KItem, minimum of 4 characters. +- **ktype_id**: The type ID of the KItem. +- **created_at**: Timestamp of when the KItem was created. +- **updated_at**: Timestamp of when the KItem was last updated. +- **summary**: A brief human-readable summary of the KItem. +- **avatar_exists**: Indicates whether the KItem has an avatar image associated with it. +- **custom_properties**: A set of custom properties related to the KItem. +- **kitem_apps**: A list of applications associated with the KItem. +- **annotations**: A list of annotations related to the KItem. +- **affiliations**: A list of affiliations associated with the KItem. +- **authors**: A list of authors related to the KItem. +- **contacts**: Contact information related to the KItem. +- **external_links**: A list of external links related to the KItem. +- **attachments**: A list of file attachments associated with the KItem. +- **hdf5**: HDF5 data structure associated with the KItem. +- **linked_kitems**: List of other KItems linked to this KItem. +- **user_groups**: User groups with access to this KItem. + +**Required Fields:** `name`, `ktype_id` + +##### 2. App ##### + +**Description:** Represents an application associated with a KItem. + +**Properties:** +- **executable**: Name of the executable file related to the app. +- **description**: Description of the application. +- **title**: Title of the application. +- **kitemAppId**: ID of the KItem App. +- **tags**: Tags related to the application. +- **additionalProperties**: Additional properties specific to the application. + +**Required Fields:** `executable` + +##### 3.Annotation ##### + +**Description:** Represents an annotation within a KItem. + +**Properties:** +- **iri**: IRI of the annotation. +- **name**: Name of the annotation. +- **namespace**: Namespace of the annotation. + +**Required Fields:** `iri`, `name`, `namespace` + +##### 4.Affiliation ##### + +**Description:** Represents an affiliation associated with a KItem. + +**Properties:** +- **name**: Name of the affiliation. + +**Required Fields:** `name` + +##### 5.Author ##### + +**Description:** Represents an author of a KItem. + +**Properties:** +- **userId**: DSMS User ID of the author. + +**Required Fields:** `userId` + +##### 6.Attachment ##### + +**Description:** Represents a file attachment within a KItem. + +**Properties:** +- **name**: File name of the attachment. + +**Required Fields:** `name` + +##### 7.External Link ##### + +**Description:** Represents an external link associated with a KItem. + +**Properties:** +- **label**: Label of the external link. +- **url**: URL of the external link. + +**Required Fields:** `label`, `url` + +##### 8.KType ##### + +**Description:** Represents the type of knowledge encapsulated in a KItem. + +**Properties:** +- **name**: Human-readable name of the KType. +- **data_schema**: OpenAPI schema of the KItem. +- **form_data**: Form data of the KItem. + +**Required Fields:** `id` + +##### 9.User Group ##### + +**Description:** Represents a user group associated with a KItem. + +**Properties:** +- **groupId**: ID of the user group. +- **name**: Name of the user group. + +**Required Fields:** `name`, `groupId` diff --git a/docs/source/tutorials/1_introduction.ipynb b/docs/source/tutorials/1_introduction.ipynb index f23468e..f491b3f 100644 --- a/docs/source/tutorials/1_introduction.ipynb +++ b/docs/source/tutorials/1_introduction.ipynb @@ -735,7 +735,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index 8c26bd5..fc2421d 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -183,7 +183,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.10.13" } }, "nbformat": 4, From ff4ab077554407c3a49c2bab71443d5be4a93de1 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 2 May 2024 12:26:36 +0000 Subject: [PATCH 058/114] First major iteration --- docs/source/tutorials/1_introduction.ipynb | 614 ++------------------- docs/source/tutorials/2_creation.ipynb | 57 +- docs/source/tutorials/3_updation.ipynb | 53 +- docs/source/tutorials/4_deletion.ipynb | 22 +- docs/source/tutorials/5_search.ipynb | 21 +- docs/source/tutorials/6_app_HDF5.ipynb | 28 +- 6 files changed, 86 insertions(+), 709 deletions(-) diff --git a/docs/source/tutorials/1_introduction.ipynb b/docs/source/tutorials/1_introduction.ipynb index f491b3f..514b1cf 100644 --- a/docs/source/tutorials/1_introduction.ipynb +++ b/docs/source/tutorials/1_introduction.ipynb @@ -26,11 +26,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -45,15 +40,22 @@ "cell_type": "code", "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "OSError", + "evalue": "File `../.env` does not exist", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[2], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m dsms \u001b[38;5;241m=\u001b[39m \u001b[43mDSMS\u001b[49m\u001b[43m(\u001b[49m\u001b[43menv\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m../.env\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/dsms-python-sdk/dsms/core/dsms.py:79\u001b[0m, in \u001b[0;36mDSMS.__init__\u001b[0;34m(self, config, env, **kwargs)\u001b[0m\n\u001b[1;32m 77\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m env:\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mexists(env):\n\u001b[0;32m---> 79\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFile `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00menv\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m` does not exist\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 80\u001b[0m loaded \u001b[38;5;241m=\u001b[39m load_dotenv(env, verbose\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[1;32m 81\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m loaded:\n", + "\u001b[0;31mOSError\u001b[0m: File `../.env` does not exist" + ] + } + ], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" ] }, { @@ -80,13 +82,15 @@ "text/plain": [ "[KItem(\n", " \n", - " \tname = foo1213, \n", + " \tname = machine 1, \n", " \n", - " \tid = 74707abb-603c-463e-9cb7-612f756027f7, \n", + " \tid = 2e235212-733d-44e5-8bda-38f13be6475e, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = testingmachine, \n", " \n", - " \tslug = foo1213, \n", + " \tin_backend = True, \n", + " \n", + " \tslug = machine-1, \n", " \n", " \tannotations = [], \n", " \n", @@ -97,16 +101,18 @@ " \taffiliations = [], \n", " \n", " \tauthors = [\n", - " \t\tAuthor(user_id=ebe82177-0c81-4f73-b152-acc787830f5e)\n", + " \t\t{\n", + " \t\t\tuser_id: 9c8a295a-aeed-46c1-87f1-2793ef78320b\n", + " \t\t}\n", " \t], \n", " \n", " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-12 13:22:18.437784, \n", + " \tcreated_at = 2024-04-30 12:58:54.233036, \n", " \n", - " \tupdated_at = 2024-04-12 13:22:18.437784, \n", + " \tupdated_at = 2024-04-30 12:58:54.233036, \n", " \n", " \texternal_links = [], \n", " \n", @@ -116,9 +122,11 @@ " \n", " \tuser_groups = [], \n", " \n", - " \tcustom_properties = CustomProperties(content={'foo': 'bar'}), \n", + " \tcustom_properties = None, \n", + " \n", + " \thdf5 = None, \n", " \n", - " \thdf5 = None\n", + " \trdf_exists = False\n", " )]" ] }, @@ -135,554 +143,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can investigate what a KItem needs in order to be created. KItems are entirely based on [`Pydantic`](https://docs.pydantic.dev/latest/)-Models (v2), hence the properties (in `Pydantic` called `Fields`) are automatically validated once we set them. \n", + "We can investigate what a KItem needs in order to be created. KItems are entirely based on Pydantic-Models (v2), hence the properties (in Pydantic called Fields) are automatically validated once we set them.\n", + "\n", + "The schema of the KItem itself is a JSON schema which is machine-readable and can be directly incorporated into Swagger-supported APIs like e.g. FastAPI.\n", "\n", - "The schema of the KItem itself is a JSON schema which is machine-readable and can be directly incorporated into [Swagger](https://swagger.io/tools/swagger-ui/)-supported APIs like e.g. [`FastAPI`](https://fastapi.tiangolo.com/)." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'$defs': {'AdditionalProperties': {'description': 'Additional properties of',\n", - " 'properties': {'triggerUponUpload': {'default': False,\n", - " 'description': 'Whether '\n", - " 'the '\n", - " 'app '\n", - " 'should '\n", - " 'be '\n", - " 'triggered '\n", - " 'when '\n", - " 'a '\n", - " 'file '\n", - " 'is '\n", - " 'uploaded',\n", - " 'title': 'Triggeruponupload',\n", - " 'type': 'boolean'},\n", - " 'triggerUponUploadFileExtensions': {'anyOf': [{'items': {'type': 'string'},\n", - " 'type': 'array'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'File '\n", - " 'extensions '\n", - " 'for '\n", - " 'which '\n", - " 'the '\n", - " 'upload '\n", - " 'shall '\n", - " 'be '\n", - " 'triggered.',\n", - " 'title': 'Triggeruponuploadfileextensions'}},\n", - " 'title': 'AdditionalProperties',\n", - " 'type': 'object'},\n", - " 'Affiliation': {'description': 'Affiliation of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'affiliation',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name'],\n", - " 'title': 'Affiliation',\n", - " 'type': 'object'},\n", - " 'Annotation': {'description': 'KItem annotation model',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'iri': {'description': 'IRI of the '\n", - " 'annotation',\n", - " 'title': 'Iri',\n", - " 'type': 'string'},\n", - " 'name': {'description': 'Name of the '\n", - " 'annotation',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'namespace': {'description': 'Namespace '\n", - " 'of the '\n", - " 'annotation',\n", - " 'title': 'Namespace',\n", - " 'type': 'string'}},\n", - " 'required': ['iri', 'name', 'namespace'],\n", - " 'title': 'Annotation',\n", - " 'type': 'object'},\n", - " 'App': {'description': 'App of a KItem.',\n", - " 'properties': {'additionalProperties': {'anyOf': [{'$ref': '#/$defs/AdditionalProperties'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Additional '\n", - " 'properties '\n", - " 'related '\n", - " 'to '\n", - " 'the '\n", - " 'appilcation'},\n", - " 'description': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Description '\n", - " 'of the '\n", - " 'appilcation',\n", - " 'title': 'Description'},\n", - " 'executable': {'description': 'Name of the '\n", - " 'executable '\n", - " 'related to '\n", - " 'the app',\n", - " 'title': 'Executable',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related to '\n", - " 'the KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'kitemAppId': {'anyOf': [{'type': 'integer'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'ID of the '\n", - " 'KItem App',\n", - " 'title': 'Kitemappid'},\n", - " 'tags': {'anyOf': [{'type': 'object'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Tags related to the '\n", - " 'appilcation',\n", - " 'title': 'Tags'},\n", - " 'title': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Title of the '\n", - " 'appilcation',\n", - " 'title': 'Title'}},\n", - " 'required': ['executable'],\n", - " 'title': 'App',\n", - " 'type': 'object'},\n", - " 'Attachment': {'description': 'Attachment uploaded by a certain '\n", - " 'user.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'File name of '\n", - " 'the '\n", - " 'attachment',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name'],\n", - " 'title': 'Attachment',\n", - " 'type': 'object'},\n", - " 'Author': {'description': 'Author of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related '\n", - " 'to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'userId': {'description': 'ID of the DSMS '\n", - " 'User',\n", - " 'format': 'uuid',\n", - " 'title': 'Userid',\n", - " 'type': 'string'}},\n", - " 'required': ['userId'],\n", - " 'title': 'Author',\n", - " 'type': 'object'},\n", - " 'Column': {'description': 'Column of an HDF5 data frame',\n", - " 'properties': {'columnId': {'description': 'Column ID in '\n", - " 'the data '\n", - " 'frame',\n", - " 'title': 'Columnid',\n", - " 'type': 'integer'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID related '\n", - " 'to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'column in the '\n", - " 'data series.',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['columnId', 'name'],\n", - " 'title': 'Column',\n", - " 'type': 'object'},\n", - " 'ContactInfo': {'description': 'Contact info',\n", - " 'properties': {'email': {'description': 'EMail of '\n", - " 'the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Email',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'userId': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'User ID '\n", - " 'of the '\n", - " 'contact '\n", - " 'person',\n", - " 'title': 'Userid'}},\n", - " 'required': ['name', 'email'],\n", - " 'title': 'ContactInfo',\n", - " 'type': 'object'},\n", - " 'ExternalLink': {'description': 'External link of a KItem.',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to '\n", - " 'the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'label': {'description': 'Label of '\n", - " 'the '\n", - " 'external '\n", - " 'link',\n", - " 'title': 'Label',\n", - " 'type': 'string'},\n", - " 'url': {'description': 'URL of the '\n", - " 'external '\n", - " 'link',\n", - " 'format': 'uri',\n", - " 'minLength': 1,\n", - " 'title': 'Url',\n", - " 'type': 'string'}},\n", - " 'required': ['label', 'url'],\n", - " 'title': 'ExternalLink',\n", - " 'type': 'object'},\n", - " 'KItem': {'additionalProperties': False,\n", - " 'description': 'Knowledge Item of the DSMS.',\n", - " 'properties': {'affiliations': {'default': [],\n", - " 'description': 'Affiliations '\n", - " 'related '\n", - " 'to a '\n", - " 'KItem.',\n", - " 'items': {'$ref': '#/$defs/Affiliation'},\n", - " 'title': 'Affiliations',\n", - " 'type': 'array'},\n", - " 'annotations': {'default': [],\n", - " 'description': 'Annotations '\n", - " 'of the '\n", - " 'KItem',\n", - " 'items': {'$ref': '#/$defs/Annotation'},\n", - " 'title': 'Annotations',\n", - " 'type': 'array'},\n", - " 'attachments': {'default': [],\n", - " 'description': 'File '\n", - " 'attachements '\n", - " 'of the '\n", - " 'DSMS',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/Attachment'},\n", - " {'type': 'string'}]},\n", - " 'title': 'Attachments',\n", - " 'type': 'array'},\n", - " 'authors': {'default': [],\n", - " 'description': 'Authorship of '\n", - " 'the KItem.',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/Author'},\n", - " {'type': 'string'}]},\n", - " 'title': 'Authors',\n", - " 'type': 'array'},\n", - " 'avatar_exists': {'anyOf': [{'type': 'boolean'},\n", - " {'type': 'null'}],\n", - " 'default': False,\n", - " 'description': 'Whether '\n", - " 'the '\n", - " 'KItem '\n", - " 'holds an '\n", - " 'avatar '\n", - " 'or not.',\n", - " 'title': 'Avatar Exists'},\n", - " 'contacts': {'default': [],\n", - " 'description': 'Whether the '\n", - " 'KItem holds '\n", - " 'any contact '\n", - " 'information.',\n", - " 'items': {'$ref': '#/$defs/ContactInfo'},\n", - " 'title': 'Contacts',\n", - " 'type': 'array'},\n", - " 'created_at': {'anyOf': [{'type': 'string'},\n", - " {'format': 'date-time',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Time and '\n", - " 'date when '\n", - " 'the KItem '\n", - " 'was '\n", - " 'created.',\n", - " 'title': 'Created At'},\n", - " 'custom_properties': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': {},\n", - " 'description': 'Custom '\n", - " 'properties '\n", - " 'associated '\n", - " 'to '\n", - " 'the '\n", - " 'KItem',\n", - " 'title': 'Custom '\n", - " 'Properties'},\n", - " 'external_links': {'default': [],\n", - " 'description': 'External '\n", - " 'links '\n", - " 'related '\n", - " 'to the '\n", - " 'KItem',\n", - " 'items': {'$ref': '#/$defs/ExternalLink'},\n", - " 'title': 'External '\n", - " 'Links',\n", - " 'type': 'array'},\n", - " 'hdf5': {'anyOf': [{'items': {'$ref': '#/$defs/Column'},\n", - " 'type': 'array'},\n", - " {'additionalProperties': {'anyOf': [{'items': {},\n", - " 'type': 'array'},\n", - " {'type': 'object'}]},\n", - " 'type': 'object'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'HDF5 interface.',\n", - " 'title': 'Hdf5'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'description': 'ID of the KItem',\n", - " 'title': 'Id'},\n", - " 'kitem_apps': {'default': [],\n", - " 'description': 'Apps '\n", - " 'related to '\n", - " 'the KItem.',\n", - " 'items': {'$ref': '#/$defs/App'},\n", - " 'title': 'Kitem Apps',\n", - " 'type': 'array'},\n", - " 'ktype': {'anyOf': [{'$ref': '#/$defs/KType'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KType of the '\n", - " 'KItem'},\n", - " 'ktype_id': {'anyOf': [{'description': 'Generic '\n", - " 'enumeration.\\n'\n", - " '\\n'\n", - " 'Derive '\n", - " 'from '\n", - " 'this '\n", - " 'class '\n", - " 'to '\n", - " 'define '\n", - " 'new '\n", - " 'enumerations.',\n", - " 'enum': [],\n", - " 'title': 'Enum'},\n", - " {'type': 'string'}],\n", - " 'description': 'Type ID of '\n", - " 'the KItem',\n", - " 'title': 'Ktype Id'},\n", - " 'linked_kitems': {'default': [],\n", - " 'description': 'KItems '\n", - " 'linked '\n", - " 'to the '\n", - " 'current '\n", - " 'KItem.',\n", - " 'items': {'anyOf': [{'$ref': '#/$defs/LinkedKItem'},\n", - " {'$ref': '#/$defs/KItem'}]},\n", - " 'title': 'Linked Kitems',\n", - " 'type': 'array'},\n", - " 'name': {'description': 'Human readable '\n", - " 'name of the '\n", - " 'KContext.dsms',\n", - " 'title': 'Name',\n", - " 'type': 'string'},\n", - " 'slug': {'anyOf': [{'minLength': 4,\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Slug of the '\n", - " 'KContext.dsms',\n", - " 'title': 'Slug'},\n", - " 'summary': {'anyOf': [{'type': 'string'},\n", - " {'$ref': '#/$defs/Summary'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Human readable '\n", - " 'summary text '\n", - " 'of the KItem.',\n", - " 'title': 'Summary'},\n", - " 'updated_at': {'anyOf': [{'type': 'string'},\n", - " {'format': 'date-time',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Time and '\n", - " 'date when '\n", - " 'the KItem '\n", - " 'was '\n", - " 'updated.',\n", - " 'title': 'Updated At'},\n", - " 'user_groups': {'default': [],\n", - " 'description': 'User '\n", - " 'groups '\n", - " 'able to '\n", - " 'access the '\n", - " 'KItem.',\n", - " 'items': {'$ref': '#/$defs/UserGroup'},\n", - " 'title': 'User Groups',\n", - " 'type': 'array'}},\n", - " 'required': ['name', 'ktype_id'],\n", - " 'title': 'KItem',\n", - " 'type': 'object'},\n", - " 'KType': {'description': 'Knowledge type of the knowledge item.',\n", - " 'properties': {'data_schema': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'OpenAPI '\n", - " 'schema of '\n", - " 'the KItem.',\n", - " 'title': 'Data Schema'},\n", - " 'form_data': {'anyOf': [{},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Form data of '\n", - " 'the KItem.',\n", - " 'title': 'Form Data'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'string'}],\n", - " 'description': 'ID of the KType.',\n", - " 'title': 'Id'},\n", - " 'name': {'anyOf': [{'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Human readable '\n", - " 'name of the '\n", - " 'KType.',\n", - " 'title': 'Name'}},\n", - " 'required': ['id'],\n", - " 'title': 'KType',\n", - " 'type': 'object'},\n", - " 'LinkedKItem': {'description': 'Data model of a linked KItem',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'ID of the '\n", - " 'KItem to be '\n", - " 'linked',\n", - " 'title': 'Id'},\n", - " 'sourceId': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'Source '\n", - " 'ID of '\n", - " 'the '\n", - " 'KItem',\n", - " 'title': 'Sourceid'}},\n", - " 'title': 'LinkedKItem',\n", - " 'type': 'object'},\n", - " 'Summary': {'additionalProperties': False,\n", - " 'description': 'Model for the custom properties of the '\n", - " 'KItem',\n", - " 'properties': {'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'title': 'Id'},\n", - " 'kitem': {'anyOf': [{}, {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem related '\n", - " 'to the summary',\n", - " 'title': 'Kitem'},\n", - " 'text': {'description': 'Summary text of '\n", - " 'the KItem',\n", - " 'title': 'Text',\n", - " 'type': 'string'}},\n", - " 'required': ['text'],\n", - " 'title': 'Summary',\n", - " 'type': 'object'},\n", - " 'UserGroup': {'description': 'Users groups related to a KItem.',\n", - " 'properties': {'groupId': {'description': 'ID of the '\n", - " 'user group',\n", - " 'title': 'Groupid',\n", - " 'type': 'string'},\n", - " 'id': {'anyOf': [{'format': 'uuid',\n", - " 'type': 'string'},\n", - " {'type': 'null'}],\n", - " 'default': None,\n", - " 'description': 'KItem ID '\n", - " 'related to the '\n", - " 'KPropertyItem',\n", - " 'title': 'Id'},\n", - " 'name': {'description': 'Name of the '\n", - " 'user group',\n", - " 'title': 'Name',\n", - " 'type': 'string'}},\n", - " 'required': ['name', 'groupId'],\n", - " 'title': 'UserGroup',\n", - " 'type': 'object'}},\n", - " 'allOf': [{'$ref': '#/$defs/KItem'}]}\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Anaconda3\\envs\\hiwi\\lib\\site-packages\\pydantic\\json_schema.py:2158: PydanticJsonSchemaWarning: Default value is not JSON serializable; excluding default from JSON schema [non-serializable-default]\n", - " warnings.warn(message, PydanticJsonSchemaWarning)\n" - ] - } - ], - "source": [ - "pprint(KItem.model_json_schema())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "We can investigate the KTypes defined in the remote instance:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -693,23 +163,9 @@ "KTypes.Expert\n", "KTypes.App\n", "KTypes.DatasetCatalog\n", - "KTypes.TestSeries\n", - "KTypes.TestMatthias\n", - "KTypes.Characterizationprocedure\n", "KTypes.Dataset\n", "KTypes.Specimen\n", - "KTypes.Formtest2\n" - ] - }, - { - "ename": "", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", - "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", - "\u001b[1;31mClick here for more info. \n", - "\u001b[1;31mView Jupyter log for further details." + "KTypes.Testingmachine\n" ] } ], diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index fc2421d..bc41e2c 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -13,8 +13,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 2.1: Setting up\n", - "\n", + "### 2.1. Setting up\n", "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", "\n", "Now let us import the needed classes and functions for this tutorial." @@ -26,11 +25,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -47,13 +41,14 @@ "metadata": {}, "outputs": [], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now display what kitems we have" ] }, { @@ -82,36 +77,13 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'KItem' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m item \u001b[38;5;241m=\u001b[39m \u001b[43mKItem\u001b[49m(\n\u001b[0;32m 2\u001b[0m name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfoo123\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 3\u001b[0m ktype_id\u001b[38;5;241m=\u001b[39mdsms\u001b[38;5;241m.\u001b[39mktypes\u001b[38;5;241m.\u001b[39mDatasetCatalog,\n\u001b[0;32m 4\u001b[0m custom_properties\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfoo\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbar\u001b[39m\u001b[38;5;124m\"\u001b[39m},\n\u001b[0;32m 5\u001b[0m )\n\u001b[0;32m 7\u001b[0m item\n", - "\u001b[1;31mNameError\u001b[0m: name 'KItem' is not defined" - ] - }, - { - "ename": "", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", - "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", - "\u001b[1;31mClick here for more info. \n", - "\u001b[1;31mView Jupyter log for further details." - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "item = KItem(\n", " name=\"foo123\",\n", - " ktype_id=dsms.ktypes.DatasetCatalog,\n", + " ktype_id=dsms.ktypes.Dataset,\n", " custom_properties={\"foo\": \"bar\"},\n", ")\n", "\n", @@ -131,7 +103,8 @@ "metadata": {}, "outputs": [], "source": [ - "dsms.commit()" + "dsms.commit()\n", + "item.url" ] }, { diff --git a/docs/source/tutorials/3_updation.ipynb b/docs/source/tutorials/3_updation.ipynb index 3089a76..e128051 100644 --- a/docs/source/tutorials/3_updation.ipynb +++ b/docs/source/tutorials/3_updation.ipynb @@ -25,11 +25,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -46,13 +41,23 @@ "metadata": {}, "outputs": [], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now display what kitems we have" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" ] }, { @@ -80,34 +85,18 @@ "metadata": {}, "outputs": [], "source": [ - "# specify the path to any arbitrary file to be uploaded\n", - "file = os.path.join(\"..\", \"README.md\")\n", - "\n", "item.name = \"foobar\"\n", - "item.custom_properties.update({\"foobar\": \"foobar\"})\n", - "item.attachments.append({\"name\": file})\n", - "item.annotations.append(\n", - " {\n", - " \"iri\": \"www.example.org/foo\",\n", - " \"name\": \"example class\",\n", - " \"namespace\": \"www.example.org\",\n", - " }\n", - ")\n", + "item.custom_properties.foobar = \"foobar\"\n", + "item.attachments.append(\"../README.md\")\n", + "item.annotations.append(\"www.example.org/foo\")\n", "item.external_links.append(\n", " {\"url\": \"http://example.org\", \"label\": \"example link\"}\n", ")\n", "item.contacts.append({\"name\": \"foo\", \"email\": \"foo@bar.mail\"})\n", - "item.affiliations.append({\"name\": \"foobar team\"})\n", + "item.affiliations.append(\"foobar team\")\n", "item.user_groups.append({\"name\": \"foogroup\", \"group_id\": \"123\"})" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Changes are sent to the DSMS through the `commit`-method again." - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/docs/source/tutorials/4_deletion.ipynb b/docs/source/tutorials/4_deletion.ipynb index 7023487..283409a 100644 --- a/docs/source/tutorials/4_deletion.ipynb +++ b/docs/source/tutorials/4_deletion.ipynb @@ -26,11 +26,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -47,13 +42,14 @@ "metadata": {}, "outputs": [], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now display what kitems we have" ] }, { @@ -211,7 +207,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "0.0.0" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/docs/source/tutorials/5_search.ipynb b/docs/source/tutorials/5_search.ipynb index c95300f..3349be2 100644 --- a/docs/source/tutorials/5_search.ipynb +++ b/docs/source/tutorials/5_search.ipynb @@ -26,11 +26,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -47,20 +42,14 @@ "metadata": {}, "outputs": [], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can see which kind of DSMS-object we own as a user:" + "Now display what kitems we have" ] }, { @@ -87,7 +76,7 @@ "\n", "For this purpose, we will firstly create some KItems and apply the `search`-method on the `DSMS`-object later on in order to find them again in the DSMS.\n", "\n", - "We also wnat to demonstrate here, that we can link KItems to each other in order to find e.g. a related item of type `DatasetCatalog`. For this strategy, we are using the `linked_kitems`-attribute and the `id` of the item which we would like to link.\n", + "We also want to demonstrate here, that we can link KItems to each other in order to find e.g. a related item of type `DatasetCatalog`. For this strategy, we are using the `linked_kitems`- attribute and the `id` of the item which we would like to link.\n", "\n", "The procedure looks like this:" ] @@ -218,9 +207,7 @@ "del dsms[item3]\n", "del dsms[item4]\n", "\n", - "dsms.commit()\n", - "\n", - "dsms.kitems" + "dsms.commit()" ] } ], diff --git a/docs/source/tutorials/6_app_HDF5.ipynb b/docs/source/tutorials/6_app_HDF5.ipynb index 15bf05d..b12f921 100644 --- a/docs/source/tutorials/6_app_HDF5.ipynb +++ b/docs/source/tutorials/6_app_HDF5.ipynb @@ -1,18 +1,5 @@ { "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 6. Create Kitems\n", - "\n", - "In this tutorial we see how to :\n", - "\n", - "1.Investigate which apps are available \n", - "\n", - "2.Extract kitems into dataframes via retrieval using hdf5 format." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -30,11 +17,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", "from dsms import DSMS, KItem" ] }, @@ -51,20 +33,14 @@ "metadata": {}, "outputs": [], "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" + "dsms = DSMS(env=\"../.env\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can see which kind of DSMS-object we own as a user:" + "Now display what kitems we have" ] }, { From d72a723f51bfaee16f7db5c904d37a3068d165ed Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Fri, 3 May 2024 15:33:10 +0000 Subject: [PATCH 059/114] Add heading in app availability and HDF5 Jupyter notebook and 1st complete iteration of Kitem schema documentation. --- docs/source/introduction.md | 370 ++++++++++++++++++++----- docs/source/tutorials/6_app_HDF5.ipynb | 9 + 2 files changed, 309 insertions(+), 70 deletions(-) diff --git a/docs/source/introduction.md b/docs/source/introduction.md index 13ce8b8..6919657 100644 --- a/docs/source/introduction.md +++ b/docs/source/introduction.md @@ -69,115 +69,345 @@ For the DSMS, Pydantic has been used extensively. Pydantic is a Python library t Below given is the details of the schema of a Kitem for better understanding: -#### KItem Schema Documentation #### +### 1.2.3. Kitem Properties -All the below kitem schema elements have id which is automatically generated which has the type - `UUID`. +A Kitem has several properties to enable to handle data effectively. This section briefly describes the properties a Kitem can consist or in simple words the schema of a K-item. -##### 1. KItem ##### +The schema contains complex types and references, indicating an advanced usage scenario where various objects (like KItems and their properties) are interconnected. It also includes customizations like optional and default values, arrays of references, and conditional formats (e.g., UUID formats). + +For the DSMS, Pydantic has been used extensively. Pydantic is a Python library that leverages type annotations for data validation and settings management. It ensures that data conforms to predefined schemas by validating or serializing it at runtime and thus facilitates strict type checking and data integrity. + +Below given is the details of the schema of a Kitem for better understanding: -**Description:** Represents a Knowledge Item within the DSMS system. -**Properties:** -- **name**: Human-readable name of the KItem. -- **slug**: A unique slug identifier for the KItem, minimum of 4 characters. -- **ktype_id**: The type ID of the KItem. -- **created_at**: Timestamp of when the KItem was created. -- **updated_at**: Timestamp of when the KItem was last updated. -- **summary**: A brief human-readable summary of the KItem. -- **avatar_exists**: Indicates whether the KItem has an avatar image associated with it. -- **custom_properties**: A set of custom properties related to the KItem. -- **kitem_apps**: A list of applications associated with the KItem. -- **annotations**: A list of annotations related to the KItem. -- **affiliations**: A list of affiliations associated with the KItem. -- **authors**: A list of authors related to the KItem. -- **contacts**: Contact information related to the KItem. -- **external_links**: A list of external links related to the KItem. -- **attachments**: A list of file attachments associated with the KItem. -- **hdf5**: HDF5 data structure associated with the KItem. -- **linked_kitems**: List of other KItems linked to this KItem. -- **user_groups**: User groups with access to this KItem. +# DSMS Schema Documentation + +**Note:** This documentation covers the various components within the DSMS schema, focusing on the properties, types, and roles of different objects. + +## Definitions + +### General Definitions + +All IDs are typically of the type `UUID` unless specified otherwise. Each entity defined within the system has an associated ID which is automatically generated. + +### 1. KItem + +**Description:** Represents a Knowledge Item within the DSMS system. **Required Fields:** `name`, `ktype_id` -##### 2. App ##### +#### KItem Properties + +1.1. **Name** +- **Description:** Human-readable name of the KItem. +- **Type:** string +- **Default:** - + +1.2. **Slug** +- **Description:** A unique slug identifier for the KItem, minimum of 4 characters. +- **Type:** string, MinLength: 4 +- **Default:** None + +1.3. **Ktype_id** +- **Description:** The type ID of the KItem. +- **Type:** string or enumeration +- **Default:** - + +1.4. **Created_at** +- **Description:** Timestamp of when the KItem was created. +- **Type:** string, format: date-time +- **Default:** None + +1.5. **Updated_at** +- **Description:** Timestamp of when the KItem was last updated. +- **Type:** string, format: date-time +- **Default:** None + +1.6. **Summary** +- **Description:** A brief human-readable summary of the KItem. +- **Type:** string or summary object +- **Default:** None + +1.7. **Avatar_exists** +- **Description:** Indicates whether the KItem has an avatar image associated with it. +- **Type:** boolean +- **Default:** False + +1.8. **Custom_properties** +- **Description:** A set of custom properties related to the KItem. +- **Type:** object +- **Default:** {} + +1.9. **Kitem_apps** +- **Description:** A list of applications associated with the KItem. +- **Type:** array of App objects +- **Default:** [] + +1.10. **Annotations** +- **Description:** A list of annotations related to the KItem. +- **Type:** array of Annotation objects +- **Default:** [] + +1.11. **Affiliations** +- **Description:** A list of affiliations associated with the KItem. +- **Type:** array of Affiliation objects +- **Default:** [] + +1.12. **Authors** +- **Description:** A list of authors related to the KItem. +- **Type:** array of Author objects +- **Default:** [] + +1.13. **Contacts** +- **Description:** Contact information related to the KItem. +- **Type:** array of ContactInfo objects +- **Default:** [] + +1.14. **External_links** +- **Description:** A list of external links related to the KItem. +- **Type:** array of ExternalLink objects +- **Default:** [] + +1.15. **Attachments** +- **Description:** A list of file attachments associated with the KItem. +- **Type:** array of Attachment objects or strings +- **Default:** [] + +1.16. **Hdf5** +- **Description:** HDF5 data structure associated with the KItem. +- **Type:** array of Column objects or object +- **Default:** None + +1.17. **Linked_kitems** +- **Description:** List of other KItems linked to this KItem. +- **Type:** array of LinkedKItem or KItem objects +- **Default:** [] + +1.18. **User_groups** +- **Description:** User groups with access to this KItem. +- **Type:** array of UserGroup objects +- **Default:** [] + +### 2. App **Description:** Represents an application associated with a KItem. -**Properties:** -- **executable**: Name of the executable file related to the app. -- **description**: Description of the application. -- **title**: Title of the application. -- **kitemAppId**: ID of the KItem App. -- **tags**: Tags related to the application. -- **additionalProperties**: Additional properties specific to the application. - **Required Fields:** `executable` -##### 3.Annotation ##### +#### App Properties -**Description:** Represents an annotation within a KItem. +2.1. **Executable** +- **Description:** Name of the executable related to the app. +- **Type:** string +- **Default:** - -**Properties:** -- **iri**: IRI of the annotation. -- **name**: Name of the annotation. -- **namespace**: Namespace of the annotation. +2.2. **Description** +- **Description:** Description of the application. +- **Type:** string or null +- **Default:** None -**Required Fields:** `iri`, `name`, `namespace` +2.3. **KitemAppId** +- **Description:** ID of the KItem App. +- **Type:** integer or null +- **Default:** None + +2.4. **Tags** +- **Description:** Tags related to the application. +- **Type:** object or null +- **Default:** None + +2.5. **Title** +- **Description:** Title of the application. +- **Type:** string or null +- **Default:** None -##### 4.Affiliation ##### +2.6. **Additional Properties** +- **Description:** Additional properties related to the application. +- **Type:** Refers to `AdditionalProperties` +- **Default:** None + +### 3. Affiliation **Description:** Represents an affiliation associated with a KItem. -**Properties:** -- **name**: Name of the affiliation. +**Required Fields:** `Name` + +#### Affiliation Properties + +3.1. **Name** +- **Description:** Name of the affiliation. +- **Type:** string +- **Default:** - + +### 4. Annotation + +**Description:** Represents an annotation within a KItem. + +**Required Fields:** `iri`, `name`, `namespace` + +#### Annotation Properties + +4.1. **Iri** +- **Description:** IRI of the annotation. +- **Type:** string +- **Default:** - + +4.2. **Name** +- **Description:** Name of the annotation. +- **Type:** string +- **Default:** - + +4.3. **Namespace** +- **Description:** Namespace of the annotation. +- **Type:** string +- **Default:** - + +### 5. Attachment + +**Description:** Represents a file attachment uploaded by a certain user. **Required Fields:** `name` -##### 5.Author ##### +#### Attachment Properties + +5.1. **Name** +- **Description:** File name of the attachment. +- **Type:** string +- **Default:** - + +### 6. Author **Description:** Represents an author of a KItem. -**Properties:** -- **userId**: DSMS User ID of the author. +**Required Fields:** `UserID` -**Required Fields:** `userId` +#### Author Properties -##### 6.Attachment ##### +6.1. **UserId** +- **Description:** ID of the DSMS User. +- **Type:** string (UUID) +- **Default:** - -**Description:** Represents a file attachment within a KItem. +### 7. Column -**Properties:** -- **name**: File name of the attachment. +**Description:** Represents a column of an HDF5 data frame. -**Required Fields:** `name` +**Required Fields:** `ColumnId`, `Name` + +#### Column Properties + +7.1. **ColumnId** +- **Description:** Column ID in the data frame. +- **Type:** integer +- **Default:** - + +7.2. **Name** +- **Description:** Name of the column in the data series. +- **Type:** string +- **Default:** - + +### 8. ContactInfo + +**Description:** Contact information for a person or entity. + +**Required Fields:** `name`, `email` + +#### ContactInfo Properties + +8.1. **Email** +- **Description:** Email of the contact person. +- **Type:** string +- **Default:** - + + +8.2. **Name** +- **Description:** Name of the contact person. +- **Type:** string +- **Default:** - + +8.3. **UserId** +- **Description:** User ID of the contact person. +- **Type:** string (UUID) or null +- **Default:** None + +### 9. ExternalLink + +**Description:** Represents an external link of a KItem. + +**Required Fields:** `label`, `Url` + +#### ExternalLink Properties + +9.1. **Label** +- **Description:** Label of the external link. +- **Type:** string +- **Default:** - + +9.2. **Url** +- **Description:** URL of the external link. +- **Type:** string, format: uri, minLength: 1 +- **Default:** - + +### 10. LinkedKItem + +**Description:** Data model for a linked KItem. + +**Required Fields:** None + +#### LinkedKItem Properties + +10.1. **Id** +- **Description:** ID of the KItem to be linked. +- **Type:** string (UUID) or null +- **Default:** None + +10.2. **SourceId** +- **Description:** Source ID of the KItem. +- **Type:** string (UUID) or null +- **Default:** None + +### 11. Summary + +**Description:** Model for the custom properties of the KItem. -##### 7.External Link ##### +**Required Fields:** `text` -**Description:** Represents an external link associated with a KItem. +#### Summary Properties -**Properties:** -- **label**: Label of the external link. -- **url**: URL of the external link. +11.1. **Id** +- **Description:** KItem ID. +- **Type:** string (UUID) or null +- **Default:** None -**Required Fields:** `label`, `url` +11.2. **Kitem** +- **Description:** KItem related to the summary. +- **Type:** null or object +- **Default:** None -##### 8.KType ##### +11.3. **Text** +- **Description:** Summary text of the KItem. +- **Type:** string +- **Default:** - -**Description:** Represents the type of knowledge encapsulated in a KItem. +### 12. UserGroup -**Properties:** -- **name**: Human-readable name of the KType. -- **data_schema**: OpenAPI schema of the KItem. -- **form_data**: Form data of the KItem. +**Description:** User groups related to a KItem. -**Required Fields:** `id` +**Required Fields:** `GroupId`, `name` -##### 9.User Group ##### +#### UserGroup Properties -**Description:** Represents a user group associated with a KItem. +12.1. **GroupId** +- **Description:** ID of the user group. +- **Type:** string +- **Default:** - -**Properties:** -- **groupId**: ID of the user group. -- **name**: Name of the user group. +12.2. **Id** +- **Description:** KItem ID related to the KPropertyItem. +- **Type:** string (UUID) or null +- **Default:** None -**Required Fields:** `name`, `groupId` +12.3. **Name** +- **Description:** Name of the user group. +- **Type:** string +- **Default:** - diff --git a/docs/source/tutorials/6_app_HDF5.ipynb b/docs/source/tutorials/6_app_HDF5.ipynb index b12f921..44af455 100644 --- a/docs/source/tutorials/6_app_HDF5.ipynb +++ b/docs/source/tutorials/6_app_HDF5.ipynb @@ -1,5 +1,14 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6. App availability and HDF5\n", + "\n", + "In this tutorial we see how to search existing Kitems" + ] + }, { "cell_type": "markdown", "metadata": {}, From e1c9281dce6f8714ca45e88ed117026c73c8e3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 7 May 2024 12:14:00 +0200 Subject: [PATCH 060/114] add logging messages for debugging --- dsms/knowledge/kitem.py | 18 ++++++++++++++++ dsms/knowledge/utils.py | 46 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 906a7dd..d84fc21 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -1,6 +1,7 @@ """Knowledge Item implementation of the DSMS""" import json +import logging from datetime import datetime from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union @@ -61,6 +62,8 @@ from dsms import Context from dsms.core.dsms import DSMS +logger = logging.getLogger(__name__) + class KItem(BaseModel): """ @@ -199,6 +202,10 @@ def __init__(self, **kwargs: "Any") -> None: # add kitem to buffer if not self.in_backend and self.id not in self.context.buffers.created: + logger.debug( + "Setting KItem with `%s` as created and updated during KItem initialization.", + self.id, + ) self.context.buffers.created.update({self.id: self}) self.context.buffers.updated.update({self.id: self}) @@ -207,12 +214,18 @@ def __init__(self, **kwargs: "Any") -> None: def __setattr__(self, name, value) -> None: """Add kitem to updated-buffer if an attribute is set""" super().__setattr__(name, value) + logger.debug( + "Setting property with key `%s` on KItem level: %s.", name, value + ) self._set_kitem_for_properties() if ( self.id not in self.context.buffers.updated and not name.startswith("_") ): + logger.debug( + "Setting KItem with `%s` as updated KItem.__setattr__" + ) self.context.buffers.updated.update({self.id: self}) def __str__(self) -> str: @@ -529,6 +542,11 @@ def _set_kitem_for_properties(self) -> None: """ for prop in self.__dict__.values(): if isinstance(prop, (KProperty, Summary)) and not prop.kitem: + logger.debug( + "Setting kitem with ID `%s` for property `%s` on KItem level", + self.id, + type(prop), + ) prop.kitem = self @property diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 52391dd..2363a89 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -1,6 +1,7 @@ """DSMS knowledge utilities""" import io import json +import logging import re import warnings from enum import Enum @@ -31,6 +32,8 @@ from dsms.core.context import Buffers from dsms.knowledge import KItem, KType +logger = logging.getLogger(__name__) + def _is_number(value): try: @@ -96,6 +99,7 @@ def _create_custom_properties_model( setattr(model, "__str__", _print_properties) setattr(model, "__repr__", _print_properties) setattr(model, "__setattr__", __setattr_property__) + logging.debug("Create custom properties model with fields: %s", fields) return model @@ -111,11 +115,20 @@ def _print_properties(self: Any) -> str: def __setattr_property__(self, key, value) -> None: + logger.debug( + "Setting property for custom property with key `%s` with value `%s`.", + key, + value, + ) if _is_number(value): # convert to convertable numeric object value = _create_numerical_dtype(key, value, self.kitem) # mark as updated if key != "kitem" and self.kitem: + logger.debug( + "Setting related kitem for custom properties with id `%s` as updated", + self.kitem.id, + ) self.kitem.context.buffers.updated.update({self.kitem.id: self.kitem}) elif key == "kitem": # set kitem for convertable numeric datatype @@ -161,9 +174,14 @@ def _get_remote_ktypes() -> Enum: raise ConnectionError( f"Something went wrong fetching the remote ktypes: {response.text}" ) + Context.ktypes = {ktype["id"]: KType(**ktype) for ktype in response.json()} - return Enum("KTypes", {_name_to_camel(key): key for key in Context.ktypes}) + ktypes = Enum( + "KTypes", {_name_to_camel(key): key for key in Context.ktypes} + ) + logger.debug("Got the following ktypes from backend: `%s`.", list(ktypes)) + return ktypes def _get_kitem_list() -> "List[KItem]": @@ -227,6 +245,7 @@ def _create_new_kitem(kitem: "KItem") -> None: "slug": kitem.slug, "ktype_id": kitem.ktype.id, } + logger.debug("Create new KItem with payload: %s", payload) response = _perform_request("api/knowledge/kitems", "post", json=payload) if not response.ok: raise ValueError( @@ -271,6 +290,7 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: if new_kitem.custom_properties: custom_properties = new_kitem.custom_properties.model_dump() payload.update(custom_properties={"content": custom_properties}) + logger.debug("Update KItem with payload: %s", payload) response = _perform_request( f"api/knowledge/kitems/{new_kitem.id}", "put", json=payload ) @@ -283,6 +303,7 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: def _delete_kitem(kitem: "KItem") -> None: """Delete a KItem in the remote backend""" + logger.debug("Delete KItem with id: %s", kitem.id) response = _perform_request(f"api/knowledge/kitems/{kitem.id}", "delete") if not response.ok: raise ValueError( @@ -295,6 +316,11 @@ def _update_attachments( ) -> None: """Update attachments of the KItem.""" differences = _get_attachment_diffs(old_kitem, new_kitem) + logger.debug( + "Found differences in attachments for kitem with id `%s`: %s", + new_kitem.id, + differences, + ) for upload in differences["add"]: _upload_attachments(new_kitem, upload) for remove in differences["remove"]: @@ -381,6 +407,9 @@ def _get_kitems_diffs( differences[to_remove_name] = [ attr for attr in old_attr if old_attr not in new_attr ] + logger.debug( + "Found differences between new and old KItem: %s", differences + ) return differences @@ -402,8 +431,16 @@ def _commit_updated(buffer: "Dict[str, KItem]") -> None: """Commit the buffer for the `updated` buffers""" for new_kitem in buffer.values(): old_kitem = _get_kitem(new_kitem.id, as_json=True) + logger.debug( + "Fetched data from old KItem with id `%s`: %s", + new_kitem.id, + old_kitem, + ) if old_kitem: if isinstance(new_kitem.hdf5, pd.DataFrame): + logger.debug( + "New KItem data has `pd.DataFrame`. Will push as hdf5." + ) _update_hdf5(new_kitem.id, new_kitem.hdf5) new_kitem.hdf5 = _inspect_hdf5(new_kitem.id) elif isinstance(new_kitem.hdf5, type(None)) and _inspect_hdf5( @@ -414,6 +451,12 @@ def _commit_updated(buffer: "Dict[str, KItem]") -> None: _update_attachments(new_kitem, old_kitem) new_kitem.in_backend = True for key, value in _get_kitem(new_kitem.id, as_json=True).items(): + logger.debug( + "Set updated property `%s` for KItem with id `%s` after commiting: %s", + key, + new_kitem.id, + value, + ) setattr(new_kitem, key, value) @@ -536,4 +579,5 @@ def _update_hdf5(kitem_id: str, data: pd.DataFrame): def _delete_hdf5(kitem_id: str) -> Response: + logger.debug("Delete HDF5 for kitem with id `%s`.") return _perform_request(f"api/knowledge/data_api/{kitem_id}", "delete") From 914e04bb17b8c2f4cef4d0a4d92aa6be936cde93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 10 May 2024 10:02:10 +0200 Subject: [PATCH 061/114] update logging capabilities --- dsms/core/configuration.py | 29 ++++++++++++++++++++++++++++- dsms/core/logging.py | 9 +++++++++ dsms/knowledge/kitem.py | 7 +++++++ dsms/knowledge/utils.py | 17 ++++++++++++++--- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 dsms/core/logging.py diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 423a435..edb6382 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -1,11 +1,13 @@ """General config for the DSMS Python SDK""" +import logging import urllib import warnings +from enum import Enum from typing import Callable, Optional, Set, Union import requests -from pydantic import AnyUrl, Field, SecretStr, field_validator +from pydantic import AnyUrl, ConfigDict, Field, SecretStr, field_validator from pydantic_core.core_schema import ValidationInfo from pydantic_settings import BaseSettings, SettingsConfigDict @@ -16,6 +18,16 @@ DEFAULT_REPO = "knowledge-items" +class Loglevel(Enum): + """Enum mapping for default log levels""" + + DEBUG: logging.DEBUG + INFO: logging.INFO + ERROR: logging.ERROR + CRITICAL: logging.CRITICAL + WARNING: logging.WARNING + + class Configuration(BaseSettings): """General config for DSMS-SDK""" @@ -108,6 +120,21 @@ class Configuration(BaseSettings): description="Properties to hide while printing, e.g {'external_links'}", ) + loglevel: Optional[Union[Loglevel, str]] = Field( + None, description="Set level of logging messages" + ) + + model_config = ConfigDict(use_enum_values=True) + + @field_validator("loglevel") + def get_loglevel( + cls, val: Optional[Union[Loglevel, str]] + ) -> Optional[Loglevel]: + """Set log level for package""" + if val: + logging.getLogger().setLevel(val) + return val + @field_validator("units_sparql_object") def get_unit_sparql_object(cls, val: str) -> "Callable": """Source the class from the given module""" diff --git a/dsms/core/logging.py b/dsms/core/logging.py new file mode 100644 index 0000000..b37fddd --- /dev/null +++ b/dsms/core/logging.py @@ -0,0 +1,9 @@ +"""DSMS logging module""" + +import logging +import sys + +LOG_FORMAT = "[%(asctime)s - %(name)s - %(levelname)s]: %(message)s" +handler = logging.StreamHandler(sys.stdout) +formatter = logging.Formatter(LOG_FORMAT) +handler.setFormatter(formatter) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index d84fc21..78cf2f3 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -20,6 +20,7 @@ model_validator, ) +from dsms.core.logging import handler # isort:skip from dsms.knowledge.properties import ( # isort:skip Affiliation, @@ -63,6 +64,8 @@ from dsms.core.dsms import DSMS logger = logging.getLogger(__name__) +logger.addHandler(handler) +logger.propagate = False class KItem(BaseModel): @@ -193,6 +196,8 @@ def __init__(self, **kwargs: "Any") -> None: """Initialize the KItem""" from dsms import DSMS + logger.debug("Initalize KItem with model data: %s", kwargs) + # set dsms instance if not already done if not self.dsms: self.dsms = DSMS() @@ -211,6 +216,8 @@ def __init__(self, **kwargs: "Any") -> None: self._set_kitem_for_properties() + logger.debug("KItem inizialization successful.") + def __setattr__(self, name, value) -> None: """Add kitem to updated-buffer if an attribute is set""" super().__setattr__(name, value) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 2363a89..f7050ff 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -20,6 +20,8 @@ model_validator, ) +from dsms.core.logging import handler # isort:skip + from dsms.core.utils import _name_to_camel, _perform_request # isort:skip from dsms.knowledge.properties.custom_datatype import ( # isort:skip @@ -33,6 +35,8 @@ from dsms.knowledge import KItem, KType logger = logging.getLogger(__name__) +logger.addHandler(handler) +logger.propagate = False def _is_number(value): @@ -99,7 +103,7 @@ def _create_custom_properties_model( setattr(model, "__str__", _print_properties) setattr(model, "__repr__", _print_properties) setattr(model, "__setattr__", __setattr_property__) - logging.debug("Create custom properties model with fields: %s", fields) + logger.debug("Create custom properties model with fields: %s", fields) return model @@ -290,7 +294,9 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: if new_kitem.custom_properties: custom_properties = new_kitem.custom_properties.model_dump() payload.update(custom_properties={"content": custom_properties}) - logger.debug("Update KItem with payload: %s", payload) + logger.debug( + "Update KItem for `%s` with payload: %s", new_kitem.id, payload + ) response = _perform_request( f"api/knowledge/kitems/{new_kitem.id}", "put", json=payload ) @@ -416,9 +422,14 @@ def _get_kitems_diffs( def _commit(buffers: "Buffers") -> None: """Commit the buffers for the created, updated and deleted buffers""" + logger.debug("Committing KItems in buffers. Current buffers:") + logger.debug("Created: %s", buffers.created) + logger.debug("Updated: %s", buffers.updated) + logger.debug("Deleted: %s", buffers.deleted) _commit_created(buffers.created) _commit_updated(buffers.updated) _commit_deleted(buffers.deleted) + logger.debug("Committing successful, clearing buffers.") def _commit_created(buffer: "Dict[str, KItem]") -> dict: @@ -579,5 +590,5 @@ def _update_hdf5(kitem_id: str, data: pd.DataFrame): def _delete_hdf5(kitem_id: str) -> Response: - logger.debug("Delete HDF5 for kitem with id `%s`.") + logger.debug("Delete HDF5 for kitem with id `%s`.", kitem_id) return _perform_request(f"api/knowledge/data_api/{kitem_id}", "delete") From 23f868926c0e54040caed34d684e0c1f6edf482e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 10 May 2024 11:17:41 +0200 Subject: [PATCH 062/114] Bump version v1.4.0rc11 -> v1.4.0rc12 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index acc5f64..4559d2e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc11 +version = v1.4.0rc12 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 9a22c1069b98e4a229e5dac76dc7799f8a49ef4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 10 May 2024 12:28:40 +0200 Subject: [PATCH 063/114] rename kproperty and kproperty item, add more debugging messages --- dsms/knowledge/kitem.py | 21 +++-- dsms/knowledge/properties/__init__.py | 6 +- dsms/knowledge/properties/affiliations.py | 8 +- dsms/knowledge/properties/annotations.py | 10 +-- dsms/knowledge/properties/apps.py | 12 +-- dsms/knowledge/properties/attachments.py | 18 ++-- dsms/knowledge/properties/authors.py | 10 +-- dsms/knowledge/properties/base.py | 95 ++++++++++++++------- dsms/knowledge/properties/contacts.py | 10 +-- dsms/knowledge/properties/external_links.py | 10 +-- dsms/knowledge/properties/hdf5.py | 6 +- dsms/knowledge/properties/linked_kitems.py | 10 +-- dsms/knowledge/properties/user_groups.py | 10 +-- dsms/knowledge/utils.py | 9 +- 14 files changed, 141 insertions(+), 94 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 78cf2f3..25e9b9f 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -37,7 +37,7 @@ ContactsProperty, ExternalLink, ExternalLinksProperty, - KProperty, + KItemPropertyList, HDF5Container, Column, LinkedKItem, @@ -196,7 +196,7 @@ def __init__(self, **kwargs: "Any") -> None: """Initialize the KItem""" from dsms import DSMS - logger.debug("Initalize KItem with model data: %s", kwargs) + logger.debug("Initialize KItem with model data: %s", kwargs) # set dsms instance if not already done if not self.dsms: @@ -208,7 +208,7 @@ def __init__(self, **kwargs: "Any") -> None: # add kitem to buffer if not self.in_backend and self.id not in self.context.buffers.created: logger.debug( - "Setting KItem with `%s` as created and updated during KItem initialization.", + "Marking KItem with ID `%s` as created and updated during KItem initialization.", self.id, ) self.context.buffers.created.update({self.id: self}) @@ -231,7 +231,8 @@ def __setattr__(self, name, value) -> None: and not name.startswith("_") ): logger.debug( - "Setting KItem with `%s` as updated KItem.__setattr__" + "Setting KItem with ID `%s` as updated during KItem.__setattr__", + self.id, ) self.context.buffers.updated.update({self.id: self}) @@ -353,9 +354,10 @@ def validate_external_links( @field_validator("linked_kitems", mode="before") @classmethod def validate_linked_kitems_list( - cls, value: "List[Union[Dict, KItem, Any]]" + cls, value: "List[Union[Dict, KItem, Any]]", info: ValidationInfo ) -> List[LinkedKItem]: """Validate each single kitem to be linked""" + src_id = info.data.get("id") linked_kitems = [] for item in value: if isinstance(item, dict): @@ -371,6 +373,10 @@ def validate_linked_kitems_list( raise AttributeError( f"Linked KItem `{item}` has no attribute `id`." ) from error + if str(src_id) == str(dest_id): + raise ValueError( + f"Cannot link KItem with ID `{src_id}` to itself!" + ) linked_kitems.append(LinkedKItem(id=dest_id)) return linked_kitems @@ -548,7 +554,10 @@ def _set_kitem_for_properties(self) -> None: remain the context for the buffer if any of these properties is changed. """ for prop in self.__dict__.values(): - if isinstance(prop, (KProperty, Summary)) and not prop.kitem: + if ( + isinstance(prop, (KItemPropertyList, Summary)) + and not prop.kitem + ): logger.debug( "Setting kitem with ID `%s` for property `%s` on KItem level", self.id, diff --git a/dsms/knowledge/properties/__init__.py b/dsms/knowledge/properties/__init__.py index 652465f..d3594df 100644 --- a/dsms/knowledge/properties/__init__.py +++ b/dsms/knowledge/properties/__init__.py @@ -10,7 +10,7 @@ ) from dsms.knowledge.properties.apps import App, AppsProperty from dsms.knowledge.properties.authors import Author, AuthorsProperty -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.properties.contacts import ContactInfo, ContactsProperty from dsms.knowledge.properties.custom_datatype import NumericalDataType from dsms.knowledge.properties.hdf5 import Column, HDF5Container @@ -53,8 +53,8 @@ "UserGroupsProperty", "UserGroup", "Summary", - "KProperty", - "KPropertyItem", + "KItemPropertyList", + "KItemProperty", "HDF5Container", "Column", "NumericalDataType", diff --git a/dsms/knowledge/properties/affiliations.py b/dsms/knowledge/properties/affiliations.py index 98a56ee..79a67df 100644 --- a/dsms/knowledge/properties/affiliations.py +++ b/dsms/knowledge/properties/affiliations.py @@ -1,23 +1,23 @@ -"""Affiliation KProperty""" +"""Affiliation KItemPropertyList""" from typing import TYPE_CHECKING from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.properties.utils import _str_to_dict if TYPE_CHECKING: from typing import Callable -class Affiliation(KPropertyItem): +class Affiliation(KItemProperty): """Affiliation of a KItem.""" name: str = Field(..., description="Name of the affiliation") -class AffiliationsProperty(KProperty): +class AffiliationsProperty(KItemPropertyList): """Affiliations property""" # OVERRIDE diff --git a/dsms/knowledge/properties/annotations.py b/dsms/knowledge/properties/annotations.py index 568afa8..a6aab5a 100644 --- a/dsms/knowledge/properties/annotations.py +++ b/dsms/knowledge/properties/annotations.py @@ -1,17 +1,17 @@ -"""Annotations KProperty""" +"""Annotations KItemPropertyList""" from typing import TYPE_CHECKING from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.utils import _make_annotation_schema if TYPE_CHECKING: from typing import Any, Callable, Dict -class Annotation(KPropertyItem): +class Annotation(KItemProperty): """KItem annotation model""" iri: str = Field(..., description="IRI of the annotation") @@ -19,8 +19,8 @@ class Annotation(KPropertyItem): namespace: str = Field(..., description="Namespace of the annotation") -class AnnotationsProperty(KProperty): - """KProperty for annotations""" +class AnnotationsProperty(KItemPropertyList): + """KItemPropertyList for annotations""" # OVERRIDE @property diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index c1c6e65..61a7a10 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -1,10 +1,10 @@ -"""App KProperty""" +"""App KItemPropertyList""" from typing import TYPE_CHECKING, List, Optional from pydantic import BaseModel, Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList if TYPE_CHECKING: from typing import Callable @@ -23,7 +23,7 @@ class AdditionalProperties(BaseModel): ) def __str__(self) -> str: - """Pretty print the KProperty""" + """Pretty print the KItemPropertyList""" values = ", ".join( [f"{key}: {value}" for key, value in self.__dict__.items()] ) @@ -34,7 +34,7 @@ def __repr__(self) -> str: return str(self) -class App(KPropertyItem): +class App(KItemProperty): """App of a KItem.""" kitem_app_id: Optional[int] = Field( @@ -59,8 +59,8 @@ def run(self, *args, **kwargs) -> None: raise NotImplementedError -class AppsProperty(KProperty): - """KProperty for apps""" +class AppsProperty(KItemPropertyList): + """KItemPropertyList for apps""" # OVERRIDE @property diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 0f5fd61..0bd2094 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -1,11 +1,11 @@ -"""Attachment KProperty""" +"""Attachment KItemPropertyList""" from pathlib import Path from typing import TYPE_CHECKING from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.properties.utils import _str_to_dict from dsms.knowledge.utils import _get_attachment @@ -13,7 +13,7 @@ from typing import Any, Callable, Dict, Iterable, List, Union -class Attachment(KPropertyItem): +class Attachment(KItemProperty): """Attachment uploaded by a certain user.""" name: str = Field(..., description="File name of the attachment") @@ -23,8 +23,8 @@ def download(self) -> str: return _get_attachment(self.id, self.name) -class AttachmentsProperty(KProperty): - """KProperty for managing attachments.""" +class AttachmentsProperty(KItemPropertyList): + """KItemPropertyList for managing attachments.""" # OVERRIDE @property @@ -38,7 +38,7 @@ def k_property_helper(cls) -> "Callable": return _str_to_dict def extend(self, iterable: "Iterable") -> None: - """Extend KProperty with list of KPropertyItem""" + """Extend KItemPropertyList with list of KItemProperty""" from dsms import KItem to_extend = [] @@ -48,7 +48,7 @@ def extend(self, iterable: "Iterable") -> None: item = self._check_item(subitem) if not Path(item.name).stem in self.by_name: to_extend.append(item) - elif isinstance(item, (dict, KPropertyItem, KItem)): + elif isinstance(item, (dict, KItemProperty, KItem)): item = self._check_item(item) if not Path(item.name).stem in self.by_name: to_extend.append(item) @@ -60,7 +60,7 @@ def extend(self, iterable: "Iterable") -> None: super().extend(to_extend) def append(self, item: "Union[Dict, Any]") -> None: - """Append KPropertyItem to KProperty""" + """Append KItemProperty to KItemPropertyList""" item = self._check_item(item) @@ -69,7 +69,7 @@ def append(self, item: "Union[Dict, Any]") -> None: super().append(item) def insert(self, index: int, item: "Union[Dict, Any]") -> None: - """Insert KPropertyItem at KProperty at certain index""" + """Insert KItemProperty at KItemPropertyList at certain index""" item = self._check_item(item) if not Path(item.name).stem in self.by_name: diff --git a/dsms/knowledge/properties/authors.py b/dsms/knowledge/properties/authors.py index 4dafa98..5449142 100644 --- a/dsms/knowledge/properties/authors.py +++ b/dsms/knowledge/properties/authors.py @@ -1,24 +1,24 @@ -"""Author KProperty""" +"""Author KItemPropertyList""" from typing import TYPE_CHECKING from uuid import UUID from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList if TYPE_CHECKING: from typing import Callable -class Author(KPropertyItem): +class Author(KItemProperty): """Author of a KItem.""" user_id: UUID = Field(..., description="ID of the DSMS User") -class AuthorsProperty(KProperty): - """KProperty for authors""" +class AuthorsProperty(KItemPropertyList): + """KItemPropertyList for authors""" # OVERRIDE @property diff --git a/dsms/knowledge/properties/base.py b/dsms/knowledge/properties/base.py index b65f7be..f764ae5 100644 --- a/dsms/knowledge/properties/base.py +++ b/dsms/knowledge/properties/base.py @@ -1,4 +1,6 @@ """Basic property for a KItem""" + +import logging from abc import abstractmethod from typing import TYPE_CHECKING, Optional from uuid import UUID @@ -11,8 +13,13 @@ model_serializer, ) +from dsms.core.logging import handler # isort:skip + from dsms.core.utils import _snake_to_camel # isort:skip +logger = logging.getLogger(__name__) +logger.addHandler(handler) +logger.propagate = False if TYPE_CHECKING: from typing import Any, Callable, Dict, Iterable, List, Set, Union @@ -20,11 +27,11 @@ from dsms import Context, KItem -class KPropertyItem(BaseModel): - """List item for the KItem-property""" +class KItemProperty(BaseModel): + """Property of a KItem""" id: Optional[UUID] = Field( - None, description="KItem ID related to the KPropertyItem" + None, description="KItem ID related to the KItemProperty" ) model_config = ConfigDict( @@ -37,7 +44,7 @@ class KPropertyItem(BaseModel): _kitem = PrivateAttr(default=None) def __str__(self) -> str: - """Pretty print the KProperty""" + """Pretty print the KItemProperty""" values = ",\n\t\t\t".join( [ f"{key}: {value}" @@ -48,17 +55,23 @@ def __str__(self) -> str: return f"{{\n\t\t\t{values}\n\t\t}}" def __repr__(self) -> str: - """Pretty print the KProperty""" + """Pretty print the KItemProperty""" return str(self) def __setattr__(self, key: str, item: "Any") -> None: """Add KItem to updated buffer.""" + logger.debug( + "Setting property with key `%s` on KProperty level: %s.", key, item + ) if ( self.kitem and self.kitem.id not in self.context.buffers.updated and key not in ["_kitem", "kitem", "id"] ): self.context.buffers.updated.update({self.id: self.kitem}) + logger.debug( + "Setting KItem with `%s` as updated KItemProperty.__setattr__" + ) super().__setattr__(key, item) def __hash__(self) -> int: @@ -66,12 +79,12 @@ def __hash__(self) -> int: @property def kitem(cls) -> "KItem": - """KItem related to the KPropertyItem""" + """KItem related to the KItemProperty""" return cls._kitem @kitem.setter def kitem(cls, item: "KItem") -> None: - """Set KItem related to the KPropertyItem""" + """Set KItem related to the KItemProperty""" cls._kitem = item cls.id = item.id @@ -91,14 +104,14 @@ def context(cls) -> "Context": @model_serializer def serialize(self): - """Serialize KPropertItem""" + """Serialize KItemProperty""" return { key: value for key, value in self.__dict__.items() if key != "id" } -class KProperty(list): - """Basic class for an KItem-property.""" +class KItemPropertyList(list): + """List of a specific property belonging to a KItem.""" def __init__(self, *args) -> None: self._kitem: "KItem" = None @@ -108,7 +121,7 @@ def __init__(self, *args) -> None: @property @abstractmethod def k_property_item(cls) -> "Callable": - """Return the KPropertyItem-class for the KProperty""" + """Return the KItemProperty-class""" @property @abstractmethod @@ -117,40 +130,51 @@ def k_property_helper(cls) -> "Optional[Callable]": input into the k property item""" def __str__(self) -> str: - """Pretty print the KProperty""" + """Pretty print the KItemPropertyList""" values = ", \n".join(["\t\t" + repr(value) for value in self]) if values: values = f"\n{values}\n\t" return f"[{values}]" def __repr__(self) -> str: - """Pretty print the KProperty""" + """Pretty print the KItemPropertyList""" return str(self) def __hash__(self) -> int: return hash(str(self)) def __setitem__( - self, index: int, item: "Union[Dict, KPropertyItem]" + self, index: int, item: "Union[Dict, KItemPropertyList]" ) -> None: - """Add or Update KPropertyItem and add it to the updated-buffer.""" + """Add or Update KItemPropertyList and add it to the updated-buffer.""" + logger.debug( + "Setting property with index `%s` on KPropertyList level: %s.", + int, + item, + ) self._mark_as_updated() item = self._check_item(item) super().__setitem__(index, item) def __delitem__(self, index: int) -> None: - """Delete the KPropertyItem from the KProperty""" + """Delete the KItemPropertyList from the KItemProperty""" + + logger.debug( + "Deleting property with index `%s` on KPropertyList level", int + ) self._mark_as_updated() super().__delitem__(index) def __imul__(self, index: int) -> None: - """Imul the KPropertyItem""" + """Imul the KItemPropertyList""" self._mark_as_updated() super().__imul__(index) - def _get_extendables(self, iterable: "Iterable") -> "List[KPropertyItem]": + def _get_extendables( + self, iterable: "Iterable" + ) -> "List[KItemPropertyList]": from dsms import KItem to_extend = [] @@ -160,7 +184,7 @@ def _get_extendables(self, iterable: "Iterable") -> "List[KPropertyItem]": item = self._check_item(subitem) if not item in self: to_extend.append(item) - elif isinstance(item, (dict, KPropertyItem, KItem)): + elif isinstance(item, (dict, KItemPropertyList, KItem)): item = self._check_item(item) if not item in self: to_extend.append(item) @@ -170,44 +194,52 @@ def _get_extendables(self, iterable: "Iterable") -> "List[KPropertyItem]": return to_extend def extend(self, iterable: "Iterable") -> None: - """Extend KProperty with list of KPropertyItem""" + """Extend KItemPropertyList with list of KItemProperty""" to_extend = self._get_extendables(iterable) if to_extend: + logger.debug("Extending KPropertyList with %s.", to_extend) self._mark_as_updated() super().extend(to_extend) def append(self, item: "Union[Dict, Any]") -> None: - """Append KPropertyItem to KProperty""" + """Append KItemProperty to KItemPropertyList""" item = self._check_item(item) if not item in self: + logger.debug("Extending KPropertyList with %s.", item) self._mark_as_updated() super().append(item) def insert(self, index: int, item: "Union[Dict, Any]") -> None: - """Insert KPropertyItem at KProperty at certain index""" + """Insert KItemProperty at KItemPropertyList at certain index""" item = self._check_item(item) if not item in self: + logger.debug("Inserting into KPropertyList: %s.", item) self._mark_as_updated() super().insert(index, item) - def pop(self, index=-1) -> KPropertyItem: - """Pop KPropertyItem from KProperty""" + def pop(self, index=-1) -> KItemProperty: + """Pop KItemProperty from KItemPropertyList""" item = super().pop(index) self._mark_as_updated() + logger.debug( + "Popping KPropertyList with index `%s`: %s.", index, item + ) return item def remove(self, item: "Union[Dict, Any]") -> None: - """Remove KPropertyItem from KProperty""" + """Remove KItemProperty from KItemPropertyList""" + + logger.debug("Remove from KPropertyList: %s.", item) self._mark_as_updated() super().remove(item) - def _check_item(self, item: "Union[Dict, Any]") -> KPropertyItem: + def _check_item(self, item: "Union[Dict, Any]") -> KItemProperty: item = self._check_k_property_item(item) if self.kitem: item.kitem = self.kitem @@ -215,8 +247,8 @@ def _check_item(self, item: "Union[Dict, Any]") -> KPropertyItem: def _check_k_property_item( self, item: "Union[Dict, Any]" - ) -> KPropertyItem: - """Check the type of the processsed KPropertyItem""" + ) -> KItemProperty: + """Check the type of the processsed KItemProperty""" if not isinstance(item, BaseModel): if self.k_property_helper and not isinstance(item, dict): item = self.k_property_helper(item) @@ -230,8 +262,11 @@ def _check_k_property_item( return item def _mark_as_updated(self) -> None: - """Add KItem of KProperty to updated buffer""" + """Add KItem of KItemPropertyList to updated buffer""" if self._kitem and self._kitem.id not in self.context.buffers.updated: + logger.debug( + "Setting KItem with `%s` as updated on KItemPropertyList level" + ) self.context.buffers.updated.update({self._kitem.id: self._kitem}) @property @@ -257,5 +292,5 @@ def kitem(cls, value: "KItem") -> None: @property def values(cls) -> "List[Dict[str, Any]]": - """Values of the KProperty""" + """Values of the KItemPropertyList""" return list(cls) diff --git a/dsms/knowledge/properties/contacts.py b/dsms/knowledge/properties/contacts.py index bb21a8b..7070154 100644 --- a/dsms/knowledge/properties/contacts.py +++ b/dsms/knowledge/properties/contacts.py @@ -1,4 +1,4 @@ -"""DContacts KProperty""" +"""DContacts KItemPropertyList""" from typing import TYPE_CHECKING, Optional @@ -6,13 +6,13 @@ from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList if TYPE_CHECKING: from typing import Callable -class ContactInfo(KPropertyItem): +class ContactInfo(KItemProperty): """Contact info""" name: str = Field(..., description="Name of the contact person") @@ -22,8 +22,8 @@ class ContactInfo(KPropertyItem): ) -class ContactsProperty(KProperty): - """KProperty for contacts""" +class ContactsProperty(KItemPropertyList): + """KItemPropertyList for contacts""" # OVERRIDE @property diff --git a/dsms/knowledge/properties/external_links.py b/dsms/knowledge/properties/external_links.py index 081e04e..177613f 100644 --- a/dsms/knowledge/properties/external_links.py +++ b/dsms/knowledge/properties/external_links.py @@ -1,24 +1,24 @@ -"""ExternalLink KProperty""" +"""ExternalLink KItemPropertyList""" from typing import TYPE_CHECKING from pydantic import AnyUrl, Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList if TYPE_CHECKING: from typing import Callable -class ExternalLink(KPropertyItem): +class ExternalLink(KItemProperty): """External link of a KItem.""" label: str = Field(..., description="Label of the external link") url: AnyUrl = Field(..., description="URL of the external link") -class ExternalLinksProperty(KProperty): - """KProperty for external links""" +class ExternalLinksProperty(KItemPropertyList): + """KItemPropertyList for external links""" # OVERRIDE @property diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index 69a7902..dc9c279 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -5,7 +5,7 @@ import pandas as pd from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.utils import _get_hdf5_column, _is_number from dsms.knowledge.semantics.units import ( # isort:skip @@ -19,7 +19,7 @@ from typing import Any, Callable, Dict, List, Optional -class Column(KPropertyItem): +class Column(KItemProperty): """ Column of an HDF5 data frame. @@ -105,7 +105,7 @@ def convert_to( ] -class HDF5Container(KProperty): +class HDF5Container(KItemPropertyList): """HDF5 container of a data frame related to a KItem""" # OVERRIDE diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index 5429207..9d4d683 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -1,4 +1,4 @@ -"""Linked KItems KProperty""" +"""Linked KItems KItemPropertyList""" from typing import TYPE_CHECKING, Optional @@ -6,7 +6,7 @@ from pydantic import ConfigDict, Field, PrivateAttr, model_serializer -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.utils import _get_kitem if TYPE_CHECKING: @@ -23,7 +23,7 @@ def _linked_kitem_helper(kitem: "KItem"): return {"id": kitem.id} -class LinkedKItem(KPropertyItem): +class LinkedKItem(KItemProperty): """Data model of a linked KItem""" # OVERRIDE @@ -58,8 +58,8 @@ def serialize(self): return base -class LinkedKItemsProperty(KProperty): - """KProperty for linked KItems""" +class LinkedKItemsProperty(KItemPropertyList): + """KItemPropertyList for linked KItems""" # OVERRIDE @property diff --git a/dsms/knowledge/properties/user_groups.py b/dsms/knowledge/properties/user_groups.py index f43cb64..ade56ba 100644 --- a/dsms/knowledge/properties/user_groups.py +++ b/dsms/knowledge/properties/user_groups.py @@ -1,24 +1,24 @@ -"""UserGroup KProperty""" +"""UserGroup KItemPropertyList""" from typing import TYPE_CHECKING from pydantic import Field -from dsms.knowledge.properties.base import KProperty, KPropertyItem +from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList if TYPE_CHECKING: from typing import Callable -class UserGroup(KPropertyItem): +class UserGroup(KItemProperty): """Users groups related to a KItem.""" name: str = Field(..., description="Name of the user group") group_id: str = Field(..., description="ID of the user group") -class UserGroupsProperty(KProperty): - """KProperty for user_groups""" +class UserGroupsProperty(KItemPropertyList): + """KItemPropertyList for user_groups""" @property def k_property_item(cls) -> "Callable": diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index f7050ff..5dfe571 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -423,9 +423,9 @@ def _commit(buffers: "Buffers") -> None: """Commit the buffers for the created, updated and deleted buffers""" logger.debug("Committing KItems in buffers. Current buffers:") - logger.debug("Created: %s", buffers.created) - logger.debug("Updated: %s", buffers.updated) - logger.debug("Deleted: %s", buffers.deleted) + logger.debug("Current Created-buffer: %s", buffers.created) + logger.debug("Current Updated-buffer: %s", buffers.updated) + logger.debug("Current Deleted-buffer: %s", buffers.deleted) _commit_created(buffers.created) _commit_updated(buffers.updated) _commit_deleted(buffers.deleted) @@ -461,6 +461,9 @@ def _commit_updated(buffer: "Dict[str, KItem]") -> None: _update_kitem(new_kitem, old_kitem) _update_attachments(new_kitem, old_kitem) new_kitem.in_backend = True + logger.debug( + "Fetching updated KItem from remote backend: %s", new_kitem.id + ) for key, value in _get_kitem(new_kitem.id, as_json=True).items(): logger.debug( "Set updated property `%s` for KItem with id `%s` after commiting: %s", From 0252591084dc7546213331c3ebedbe1d6f565f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 13 May 2024 13:49:11 +0200 Subject: [PATCH 064/114] fix commiting changes in linked kitems and kitem apps --- dsms/core/dsms.py | 4 +- dsms/knowledge/kitem.py | 8 +- dsms/knowledge/properties/apps.py | 14 ++- dsms/knowledge/properties/authors.py | 14 ++- dsms/knowledge/properties/linked_kitems.py | 138 +++++++++++++++++++-- dsms/knowledge/utils.py | 63 +++++++--- tests/test_utils.py | 38 +++--- 7 files changed, 229 insertions(+), 50 deletions(-) diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index 505d02d..2df8d86 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -134,9 +134,9 @@ def search( return _search(query, ktypes, annotations, limit, allow_fuzzy) @property - def sparql_interface(cls) -> SparqlInterface: + def sparql_interface(self) -> SparqlInterface: """Sparql interface of the DSMS instance.""" - return cls._sparql_interface + return self._sparql_interface @property def ktypes(cls) -> "Enum": diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 25e9b9f..2727568 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -51,6 +51,7 @@ from dsms.knowledge.utils import ( # isort:skip _kitem_exists, + _get_kitem, _slug_is_available, _slugify, _inspect_hdf5, @@ -216,7 +217,7 @@ def __init__(self, **kwargs: "Any") -> None: self._set_kitem_for_properties() - logger.debug("KItem inizialization successful.") + logger.debug("KItem initialization successful.") def __setattr__(self, name, value) -> None: """Add kitem to updated-buffer if an attribute is set""" @@ -364,11 +365,14 @@ def validate_linked_kitems_list( dest_id = item.get("id") if not dest_id: raise ValueError("Linked KItem is missing `id`") + linked_model = _get_kitem(dest_id, as_json=True) elif isinstance(item, KItem): dest_id = item.id + linked_model = item.model_dump() else: try: dest_id = getattr(item, "id") + linked_model = _get_kitem(dest_id, as_json=True) except AttributeError as error: raise AttributeError( f"Linked KItem `{item}` has no attribute `id`." @@ -377,7 +381,7 @@ def validate_linked_kitems_list( raise ValueError( f"Cannot link KItem with ID `{src_id}` to itself!" ) - linked_kitems.append(LinkedKItem(id=dest_id)) + linked_kitems.append(LinkedKItem(**linked_model)) return linked_kitems @field_validator("linked_kitems", mode="after") diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 61a7a10..d89c08d 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -1,8 +1,8 @@ """App KItemPropertyList""" -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_serializer from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList @@ -54,6 +54,16 @@ class App(KItemProperty): None, description="Additional properties related to the appilcation" ) + # OVERRIDE + @model_serializer + def serialize_author(self) -> Dict[str, Any]: + """Serialize author model""" + return { + key: value + for key, value in self.__dict__.items() + if key not in ["id", "kitem_app_id"] + } + def run(self, *args, **kwargs) -> None: """Run application""" raise NotImplementedError diff --git a/dsms/knowledge/properties/authors.py b/dsms/knowledge/properties/authors.py index 5449142..2fedecd 100644 --- a/dsms/knowledge/properties/authors.py +++ b/dsms/knowledge/properties/authors.py @@ -1,9 +1,9 @@ """Author KItemPropertyList""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict from uuid import UUID -from pydantic import Field +from pydantic import Field, model_serializer from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList @@ -16,6 +16,16 @@ class Author(KItemProperty): user_id: UUID = Field(..., description="ID of the DSMS User") + # OVERRIDE + @model_serializer + def serialize_author(self) -> Dict[str, Any]: + """Serialize author model""" + return { + key: str(value) + for key, value in self.__dict__.items() + if key != "id" + } + class AuthorsProperty(KItemPropertyList): """KItemPropertyList for authors""" diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index 9d4d683..cf4e288 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -1,16 +1,34 @@ """Linked KItems KItemPropertyList""" - -from typing import TYPE_CHECKING, Optional +from datetime import datetime +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from uuid import UUID -from pydantic import ConfigDict, Field, PrivateAttr, model_serializer +from pydantic import ( # isort:skip + BaseModel, + ConfigDict, + Field, + PrivateAttr, + model_serializer, +) + +from dsms.knowledge.properties.base import ( # isort:skip + KItemProperty, + KItemPropertyList, +) + +from dsms.knowledge.properties.affiliations import Affiliation # isort:skip +from dsms.knowledge.properties.annotations import Annotation # isort:skip +from dsms.knowledge.properties.attachments import Attachment # isort:skip +from dsms.knowledge.properties.authors import Author # isort:skip +from dsms.knowledge.properties.contacts import ContactInfo # isort:skip +from dsms.knowledge.properties.external_links import ExternalLink # isort:skip +from dsms.knowledge.properties.user_groups import UserGroup # isort:skip +from dsms.knowledge.utils import _get_kitem # isort:skip -from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList -from dsms.knowledge.utils import _get_kitem if TYPE_CHECKING: - from typing import Callable, Union + from typing import Callable from dsms import KItem @@ -23,6 +41,23 @@ def _linked_kitem_helper(kitem: "KItem"): return {"id": kitem.id} +class LinkedLinkedKItem(BaseModel): + """Linked KItem of linked KItems""" + + id: str = Field(..., description="ID of a linked KItem of a linked KItem") + + def __str__(self) -> str: + """Pretty print the linked KItems of the linked KItem""" + values = ",\n\t\t\t".join( + [f"{key}: {value}" for key, value in self.__dict__.items()] + ) + return f"{{\n\t\t\t{values}\n\t\t}}" + + def __repr__(self) -> str: + """Pretty print the linked KItems of the linked KItem""" + return str(self) + + class LinkedKItem(KItemProperty): """Data model of a linked KItem""" @@ -32,10 +67,84 @@ class LinkedKItem(KItemProperty): description="ID of the KItem to be linked", ) + name: str = Field(..., description="Name of the linked KItem") + + slug: str = Field(..., description="Slug of the linked KItem") + + ktype_id: str = Field(..., description="Ktype ID of the linked KItem") + + summary: Optional[str] = Field( + None, description="Summary of the linked KItem." + ) + + avatar_exists: bool = Field( + False, description="Wether the linked KItem has an avatar." + ) + + annotations: List[Optional[Annotation]] = Field( + [], description="Annotations of the linked KItem" + ) + + linked_kitems: List[Optional[LinkedLinkedKItem]] = Field( + [], description="Linked KItems of the linked KItem" + ) + + external_links: List[Optional[ExternalLink]] = Field( + [], description="External links of the linked KItem" + ) + + contacts: List[Optional[ContactInfo]] = Field( + [], description="Contact info of the linked KItem" + ) + + authors: List[Optional[Author]] = Field( + [], description="Authors of the linked KItem" + ) + + linked_affiliations: List[Optional[Affiliation]] = Field( + [], description="Linked affiliations of the linked KItem" + ) + + attachments: List[Optional[Attachment]] = Field( + [], description="Attachment of the linked KItem" + ) + + user_groups: List[Optional[UserGroup]] = Field( + [], description="User groups of the linked KItem" + ) + + custom_properties: Optional[Any] = Field( + None, description="Custom properies of the linked KItem" + ) + + created_at: Optional[Union[str, datetime]] = Field( + None, description="Time and date when the KItem was created." + ) + updated_at: Optional[Union[str, datetime]] = Field( + None, description="Time and date when the KItem was updated." + ) + _kitem = PrivateAttr(default=None) # OVERRIDE - model_config = ConfigDict(exclude={}) + model_config = ConfigDict(exclude={}, arbitrary_types_allowed=True) + + # OVERRIDE + def __str__(self) -> str: + """Pretty print the linked KItem""" + values = ",\n\t\t\t".join( + [ + f"{key}: {value}" + for key, value in self.__dict__.items() + if key not in self.exclude + ] + ) + return f"{{\n\t\t\t{values}\n\t\t}}" + + # OVERRIDE + def __repr__(self) -> str: + """Pretty print the linked KItem""" + return str(self) # OVERRIDE @property @@ -51,11 +160,16 @@ def kitem(cls, value: "KItem") -> None: # OVERRIDE @model_serializer - def serialize(self): - """Serialize KPropertItem""" - base = {key: str(value) for key, value in self.__dict__.items()} - base.update(source_id=str(self.kitem.id)) - return base + def serialize_author(self) -> Dict[str, Any]: + """Serialize author model""" + return { + key: ( + str(value) + if key in ["updated_at", "created_at", "id"] + else value + ) + for key, value in self.__dict__.items() + } class LinkedKItemsProperty(KItemPropertyList): diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 5dfe571..ec8e745 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -1,6 +1,5 @@ """DSMS knowledge utilities""" import io -import json import logging import re import warnings @@ -259,11 +258,8 @@ def _create_new_kitem(kitem: "KItem") -> None: def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: """Update a KItem in the remote backend.""" - to_compare = new_kitem.model_dump( - include={"annotations", "linked_kitems", "user_groups", "kitem_apps"} - ) - differences = _get_kitems_diffs(old_kitem, to_compare) - dumped = new_kitem.model_dump_json( + differences = _get_kitems_diffs(old_kitem, new_kitem) + payload = new_kitem.model_dump( exclude={ "authors", "annotations", @@ -284,7 +280,6 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: }, exclude_none=True, ) - payload = json.loads(dumped) payload.update( external_links={ link.label: str(link.url) for link in new_kitem.external_links @@ -375,6 +370,40 @@ def _get_attachment(kitem_id: "KItem", file_name: str) -> str: return response.text +def _get_apps_diff( + old_kitem: "Dict[str, Any]", new_kitem: "KItem" +) -> "Dict[str, List[Dict[str, Any]]]": + """Get differences in kitem apps from previous KItem state""" + differences = {} + old_linked = old_kitem.get("kitem_apps") + new_linked = [new.model_dump() for new in new_kitem.kitem_apps] + differences["kitem_apps_to_update"] = [ + attr for attr in new_linked if attr not in old_linked + ] + differences["kitem_apps_to_remove"] = [ + attr for attr in old_linked if attr not in new_linked + ] + logger.debug("Found differences in KItem apps: %s", differences) + return differences + + +def _get_linked_diffs( + old_kitem: "Dict[str, Any]", new_kitem: "KItem" +) -> "Dict[str, List[Dict[str, UUID]]]": + """Get differences in linked kitem from previous KItem state""" + differences = {} + old_linked = [old.get("id") for old in old_kitem.get("linked_kitems")] + new_linked = [str(new_kitem.id) for new_kitem in new_kitem.linked_kitems] + differences["kitems_to_link"] = [ + {"id": attr} for attr in new_linked if attr not in old_linked + ] + differences["kitems_to_unlink"] = [ + {"id": attr} for attr in old_linked if attr not in new_linked + ] + logger.debug("Found differences in linked KItems: %s", differences) + return differences + + def _get_attachment_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): """Check which attachments should be removed and which should be added.""" return { @@ -391,31 +420,35 @@ def _get_attachment_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): } -def _get_kitems_diffs( - kitem_old: "Dict[str, Any]", kitem_new: "Dict[str, Any]" -): +def _get_kitems_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): """Get the differences in the attributes between two kitems""" differences = {} attributes = [ - ("linked_kitems", ("kitems", "link", "unlink")), ("annotations", ("annotations", "link", "unlink")), - ("kitem_apps", ("kitem_apps", "update", "remove")), ("user_groups", ("user_groups", "add", "remove")), ] + to_compare = kitem_new.model_dump(include={"annotations", "user_groups"}) for name, terms in attributes: to_add_name = terms[0] + "_to_" + terms[1] to_remove_name = terms[0] + "_to_" + terms[2] old_attr = kitem_old.get(name) - new_attr = kitem_new.get(name) + new_attr = to_compare.get(name) differences[to_add_name] = [ - attr for attr in new_attr if new_attr not in old_attr + attr for attr in new_attr if attr not in old_attr ] differences[to_remove_name] = [ - attr for attr in old_attr if old_attr not in new_attr + attr for attr in old_attr if attr not in new_attr ] logger.debug( "Found differences between new and old KItem: %s", differences ) + # linked kitems need special treatment since the linked target + # kitems also might differ in their new properties in some cases. + linked_kitems = _get_linked_diffs(kitem_old, kitem_new) + # same holds for kitem apps + kitem_apps = _get_apps_diff(kitem_old, kitem_new) + # merge with previously found differences + differences.update(**linked_kitems, **kitem_apps) return differences diff --git a/tests/test_utils.py b/tests/test_utils.py index 226c4c1..aaadd0d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -87,31 +87,39 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): expected = { "kitems_to_link": [ - obj.model_dump() for obj in kitem_new.linked_kitems - ], - "annotations_to_link": [ - obj.model_dump() for obj in kitem_new.annotations + {"id": str(obj.id)} for obj in kitem_new.linked_kitems ], + "annotations_to_link": [annotation], "user_groups_to_add": [], "kitem_apps_to_update": [ - obj.model_dump() for obj in kitem_new.kitem_apps + { + "executable": "foo.exe", + "title": None, + "description": None, + "tags": None, + "additional_properties": None, + } ], "kitems_to_unlink": [ - obj.model_dump() for obj in kitem_old.linked_kitems - ], - "annotations_to_unlink": [ - obj.model_dump() for obj in kitem_old.annotations + {"id": str(obj.id)} for obj in kitem_old.linked_kitems ], + "annotations_to_unlink": [annotation2], "user_groups_to_remove": [], "kitem_apps_to_remove": [ - obj.model_dump() for obj in kitem_old.kitem_apps + { + "executable": "bar.exe", + "title": None, + "description": None, + "tags": None, + "additional_properties": None, + } ], } - to_compare = kitem_new.model_dump( - include={"annotations", "linked_kitems", "user_groups", "kitem_apps"} - ) - diffs = _get_kitems_diffs(kitem_old.model_dump(), to_compare) - assert sorted(diffs) == sorted(expected) + diffs = _get_kitems_diffs(kitem_old.model_dump(), kitem_new) + + for key, value in diffs.items(): + assert value == expected.pop(key) + assert len(expected) == 0 @responses.activate From 3a332a4444c569ffe741bc7c42ee2bed21e0c383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 13 May 2024 14:08:16 +0200 Subject: [PATCH 065/114] update parsing of kitems --- dsms/knowledge/properties/linked_kitems.py | 26 +- examples/basic_usage.ipynb | 3524 ++++++++++++++++---- 2 files changed, 2937 insertions(+), 613 deletions(-) diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index cf4e288..a98da02 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -10,6 +10,7 @@ Field, PrivateAttr, model_serializer, + field_validator, ) from dsms.knowledge.properties.base import ( # isort:skip @@ -105,7 +106,7 @@ class LinkedKItem(KItemProperty): [], description="Linked affiliations of the linked KItem" ) - attachments: List[Optional[Attachment]] = Field( + attachments: List[Union[str, Optional[Attachment]]] = Field( [], description="Attachment of the linked KItem" ) @@ -158,10 +159,23 @@ def kitem(cls, value: "KItem") -> None: """Set KItem related to the linked KItem""" cls._kitem = value + @field_validator("attachments", mode="before") + @classmethod + def validate_attachments_before( + cls, value: List[Union[str, Attachment]] + ) -> List[Attachment]: + """Validate attachments Field""" + return [ + Attachment(name=attachment) + if isinstance(attachment, str) + else attachment + for attachment in value + ] + # OVERRIDE @model_serializer def serialize_author(self) -> Dict[str, Any]: - """Serialize author model""" + """Serialize linked kitems model""" return { key: ( str(value) @@ -171,6 +185,14 @@ def serialize_author(self) -> Dict[str, Any]: for key, value in self.__dict__.items() } + @field_validator("custom_properties") + @classmethod + def validate_custom_properties( + cls, value: Dict[str, Any] + ) -> "Dict[str, Any]": + """Validate the custom properties of the linked KItem""" + return value.get("content") or value + class LinkedKItemsProperty(KItemPropertyList): """KItemPropertyList for linked KItems""" diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 1dae37e..8f8961d 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -73,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -81,12 +81,11 @@ "output_type": "stream", "text": [ "KTypes.Organization\n", - "KTypes.Expert\n", "KTypes.App\n", - "KTypes.DatasetCatalog\n", "KTypes.Dataset\n", - "KTypes.Specimen\n", - "KTypes.Testingmachine\n" + "KTypes.DatasetCatalog\n", + "KTypes.TestingMachine\n", + "KTypes.Expert\n" ] } ], @@ -111,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -121,13 +120,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", + "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", "\n", "\tktype_id = KTypes.Dataset, \n", "\n", "\tin_backend = False, \n", "\n", - "\tslug = foo123-f99e1f7d, \n", + "\tslug = foo123-6e63e2f4, \n", "\n", "\tannotations = [], \n", "\n", @@ -165,7 +164,7 @@ ")" ] }, - "execution_count": 31, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -189,16 +188,16 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'https://stahldigital.materials-data.space/knowledge/dataset/foo123-f99e1f7d'" + "'https://bue.materials-data.space/knowledge/dataset/foo123-6e63e2f4'" ] }, - "execution_count": 32, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -217,7 +216,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -227,13 +226,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", + "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-f99e1f7d, \n", + "\tslug = foo123-6e63e2f4, \n", "\n", "\tannotations = [], \n", "\n", @@ -245,7 +244,7 @@ "\n", "\tauthors = [\n", "\t\t{\n", - "\t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + "\t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", "\t\t}\n", "\t], \n", "\n", @@ -253,9 +252,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-04-23 23:46:30.402595, \n", + "\tcreated_at = 2024-05-13 11:52:26.331260, \n", "\n", - "\tupdated_at = 2024-04-23 23:46:30.402595, \n", + "\tupdated_at = 2024-05-13 11:52:26.331260, \n", "\n", "\texternal_links = [], \n", "\n", @@ -275,7 +274,7 @@ ")" ] }, - "execution_count": 33, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -305,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -330,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -339,7 +338,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -349,13 +348,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", + "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-f99e1f7d, \n", + "\tslug = foo123-6e63e2f4, \n", "\n", "\tannotations = [\n", "\t\t{\n", @@ -381,7 +380,7 @@ "\n", "\tauthors = [\n", "\t\t{\n", - "\t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + "\t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", "\t\t}\n", "\t], \n", "\n", @@ -395,9 +394,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-04-23 23:46:30.402595, \n", + "\tcreated_at = 2024-05-13 11:52:26.331260, \n", "\n", - "\tupdated_at = 2024-04-23 23:46:33.430291, \n", + "\tupdated_at = 2024-05-13 11:52:29.850682, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -427,7 +426,7 @@ ")" ] }, - "execution_count": 36, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -452,7 +451,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -538,7 +537,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -550,17 +549,14 @@ "\t\t}" ] }, - "execution_count": 38, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "item.attachments.pop(0)\n", - "item.annotations.pop(0)\n", - "item.external_links.pop(0)\n", - "item.contacts.pop(0)\n", - "item.user_groups.pop(0)" + "item.annotations.pop(0)" ] }, { @@ -572,11 +568,11 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ - "item.affiliations = []" + "item.user_groups = []" ] }, { @@ -595,7 +591,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -604,7 +600,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -614,13 +610,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = f99e1f7d-2c56-4155-af38-c9efe03267fa, \n", + "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-f99e1f7d, \n", + "\tslug = foo123-6e63e2f4, \n", "\n", "\tannotations = [], \n", "\n", @@ -636,7 +632,7 @@ "\n", "\tauthors = [\n", "\t\t{\n", - "\t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + "\t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", "\t\t}\n", "\t], \n", "\n", @@ -650,9 +646,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-04-23 23:46:30.402595, \n", + "\tcreated_at = 2024-05-13 11:52:26.331260, \n", "\n", - "\tupdated_at = 2024-04-23 23:46:33.430291, \n", + "\tupdated_at = 2024-05-13 11:52:29.850682, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -677,7 +673,7 @@ ")" ] }, - "execution_count": 41, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -695,7 +691,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -711,7 +707,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -740,7 +736,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -777,7 +773,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -785,31 +781,98 @@ "text/plain": [ "[SearchResult(hit=KItem(\n", " \n", - " \tname = EBSD_HX340LAD, \n", + " \tname = 3D-Blechmodelle2, \n", " \n", - " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", + " \tid = 493a9075-c30c-48c2-b9d6-2da408f0ecda, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = ebsdhx340lad, \n", + " \tslug = 3d-blechmodelle2-493a9075, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: aaabc3d4-d06e-4512-9fe3-0d01f815690e,\n", + " \t\t\tname: DX56_D_FZ2_WR00_43,\n", + " \t\t\tslug: dx56_d_fz2_wr00_43-aaabc3d4,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DX56D,\n", + " \t\t\tname: DX56D,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", + " \t\t\tname: TensileTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", + " \t\t}, {\n", + " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'aaabc3d4-d06e-4512-9fe3-0d01f815690e', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DX56D', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'wes', 'SampleIdentifier-2': 'DX56_D_FZ2_WR00_43', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.55, 'SpecimenWidth': 20.04, 'TestingRate': 0.1, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", + " \t\t\tcreated_at: 2024-05-06T15:03:11.134388,\n", + " \t\t\tupdated_at: 2024-05-06T15:03:11.134388\n", + " \t\t}, \n", " \t\t{\n", - " \t\t\tname: 11533_FS_Scan1_830x830_100x.ang\n", + " \t\t\tid: 57fe5c58-38ab-4c8a-8f82-efce9422c291,\n", + " \t\t\tname: DP800_F_FZ2_WR15_97,\n", + " \t\t\tslug: dp800_f_fz2_wr15_97-57fe5c58,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DP800,\n", + " \t\t\tname: DP800,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", + " \t\t\tname: TensileTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", + " \t\t}, {\n", + " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: DP800_F_FZ2_WR15_97.csv\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '57fe5c58-38ab-4c8a-8f82-efce9422c291', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DP800', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'fuchs', 'SampleIdentifier-2': 'DP800_F_FZ2_WR15_97', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.513, 'SpecimenWidth': 20.015, 'TestingRate': 0.24, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", + " \t\t\tcreated_at: 2024-05-06T15:03:46.647966,\n", + " \t\t\tupdated_at: 2024-05-06T15:03:46.647966\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -817,9 +880,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-20 14:41:23.907652, \n", + " \tcreated_at = 2024-05-06 16:53:49.061347, \n", " \n", - " \tupdated_at = 2024-02-20 14:41:23.907652, \n", + " \tupdated_at = 2024-05-06 16:53:49.061347, \n", " \n", " \texternal_links = [], \n", " \n", @@ -837,33 +900,55 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = testtest, \n", + " \tname = foo 1, \n", " \n", - " \tid = 0f995978-9932-436a-9d76-961463457ed2, \n", + " \tid = c60cca3e-1d14-4e8e-ad2d-92bda7f975e5, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = testtest, \n", + " \tslug = foo1-c60cca3e, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/material#SinglePhaseAlloy,\n", - " \t\t\tname: SinglePhaseAlloy,\n", - " \t\t\tnamespace: material\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe,\n", + " \t\t\tname: foo 2,\n", + " \t\t\tslug: foo2-56791862,\n", + " \t\t\tktype_id: organization,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:49:54.440708,\n", + " \t\t\tupdated_at: 2024-05-13T11:49:54.440708\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: de5e5310-ae4b-4383-b1d5-1fc6cbbc5f2f\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -871,9 +956,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-14 08:44:46.504818, \n", + " \tcreated_at = 2024-05-13 11:49:53.852559, \n", " \n", - " \tupdated_at = 2024-03-14 08:44:46.504818, \n", + " \tupdated_at = 2024-05-13 11:49:53.852559, \n", " \n", " \texternal_links = [], \n", " \n", @@ -891,27 +976,1113 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = my_tensiletest, \n", + " \tname = Crashartig, \n", " \n", - " \tid = e0b09fe1-5425-4053-abc3-86e7e9e49b25, \n", + " \tid = 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = mytensiletest, \n", + " \tslug = crashartig-6d09ce07, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: 84c23b6a-508a-4148-a224-fcefce65510e,\n", + " \t\t\tname: YS1-FzR4-D1Q,\n", + " \t\t\tslug: ys1-fzr4-d1q-84c23b6a,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '84c23b6a-508a-4148-a224-fcefce65510e', 'content': {'TimeStamp': '2013-06-03 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.083616, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.029, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:04:21.918404,\n", + " \t\t\tupdated_at: 2024-05-06T15:04:21.918404\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 11f2533e-f999-4151-85e6-e4821ce94c7e,\n", + " \t\t\tname: YS1-FzR4-D2Q,\n", + " \t\t\tslug: ys1-fzr4-d2q-11f2533e,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '11f2533e-f999-4151-85e6-e4821ce94c7e', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.044512000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.003, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:05:34.366982,\n", + " \t\t\tupdated_at: 2024-05-06T15:05:34.366982\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 19422f63-1087-47e4-9476-5def1ad264a4,\n", + " \t\t\tname: YS1-FzR4-D3Q,\n", + " \t\t\tslug: ys1-fzr4-d3q-19422f63,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '19422f63-1087-47e4-9476-5def1ad264a4', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.060554, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.502, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:06:47.134938,\n", + " \t\t\tupdated_at: 2024-05-06T15:06:47.134938\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 37989929-f27f-466a-b06b-55777cfa9c21,\n", + " \t\t\tname: YS1-FzR4-D5Q,\n", + " \t\t\tslug: ys1-fzr4-d5q-37989929,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D5Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '37989929-f27f-466a-b06b-55777cfa9c21', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.082055999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.507, 'OriginalWidth': 10.008, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:07:57.132627,\n", + " \t\t\tupdated_at: 2024-05-06T15:07:57.132627\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 2d31e9eb-3b88-497b-a69c-1dca43678841,\n", + " \t\t\tname: YS1-FzR4-D6Q,\n", + " \t\t\tslug: ys1-fzr4-d6q-2d31e9eb,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D6Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '2d31e9eb-3b88-497b-a69c-1dca43678841', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.080607999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D6L', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:09:05.808242,\n", + " \t\t\tupdated_at: 2024-05-06T15:09:05.808242\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 6ebe0e55-3904-440b-98c0-7d91fac824a9,\n", + " \t\t\tname: YS1-FzR4-D7Q,\n", + " \t\t\tslug: ys1-fzr4-d7q-6ebe0e55,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D7Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '6ebe0e55-3904-440b-98c0-7d91fac824a9', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.028012, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.501, 'OriginalWidth': 10.012, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:10:13.892563,\n", + " \t\t\tupdated_at: 2024-05-06T15:10:13.892563\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 4697d3ff-dbb1-4574-9b30-a8bb28ba9fee,\n", + " \t\t\tname: YS1-FzR4-S1Q,\n", + " \t\t\tslug: ys1-fzr4-s1q-4697d3ff,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", + " \t\t\tname: H340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '4697d3ff-dbb1-4574-9b30-a8bb28ba9fee', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.04996, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.04, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:11:21.835900,\n", + " \t\t\tupdated_at: 2024-05-06T15:11:21.835900\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b51b724c-4030-4767-9b2e-76b5548c078f,\n", + " \t\t\tname: YS1-FzR4-S2Q,\n", + " \t\t\tslug: ys1-fzr4-s2q-b51b724c,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", + " \t\t\tname: H340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'b51b724c-4030-4767-9b2e-76b5548c078f', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.027936, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.498, 'OriginalWidth': 10.032, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:11:54.654278,\n", + " \t\t\tupdated_at: 2024-05-06T15:11:54.654278\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 12bed326-a605-46f4-82f3-ae980a7a0548,\n", + " \t\t\tname: YS1-FzR4-S3Q,\n", + " \t\t\tslug: ys1-fzr4-s3q-12bed326,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", + " \t\t\tname: H340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '12bed326-a605-46f4-82f3-ae980a7a0548', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.025976, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.024, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:12:27.310845,\n", + " \t\t\tupdated_at: 2024-05-06T15:12:27.310845\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 31566fde-4356-4c3f-ae2a-63c1a7e8da0d,\n", + " \t\t\tname: YS2-FzR4-D1Q,\n", + " \t\t\tslug: ys2-fzr4-d1q-31566fde,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '31566fde-4356-4c3f-ae2a-63c1a7e8da0d', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.397029999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.529, 'OriginalWidth': 10.07, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:12:59.925719,\n", + " \t\t\tupdated_at: 2024-05-06T15:12:59.925719\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 13c64610-8251-443e-9560-4d2297a10543,\n", + " \t\t\tname: YS2-FzR4-D2Q,\n", + " \t\t\tslug: ys2-fzr4-d2q-13c64610,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '13c64610-8251-443e-9560-4d2297a10543', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.316811, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.523, 'OriginalWidth': 10.057, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:14:10.649454,\n", + " \t\t\tupdated_at: 2024-05-06T15:14:10.649454\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b704fa17-83f4-434d-8807-14b201f35659,\n", + " \t\t\tname: YS2-FzR4-D3Q,\n", + " \t\t\tslug: ys2-fzr4-d3q-b704fa17,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'b704fa17-83f4-434d-8807-14b201f35659', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.366201, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.527, 'OriginalWidth': 10.063, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:15:23.600966,\n", + " \t\t\tupdated_at: 2024-05-06T15:15:23.600966\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 7dd2b925-7bf7-4cc0-9a78-aac7342aa283,\n", + " \t\t\tname: YS2-FzR4-D5Q,\n", + " \t\t\tslug: ys2-fzr4-d5q-7dd2b925,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D5Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '7dd2b925-7bf7-4cc0-9a78-aac7342aa283', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.351816000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.528, 'OriginalWidth': 10.047, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:16:32.952408,\n", + " \t\t\tupdated_at: 2024-05-06T15:16:32.952408\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 14b75028-fb80-4e11-8419-b4f00dd365fa,\n", + " \t\t\tname: YS2-FzR4-D6Q,\n", + " \t\t\tslug: ys2-fzr4-d6q-14b75028,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D6Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '14b75028-fb80-4e11-8419-b4f00dd365fa', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.38894, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D6Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.532, 'OriginalWidth': 10.045, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:17:43.978073,\n", + " \t\t\tupdated_at: 2024-05-06T15:17:43.978073\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 1b091b58-9aa0-4c52-9aa3-d2a571317f88,\n", + " \t\t\tname: YS2-FzR4-D7Q,\n", + " \t\t\tslug: ys2-fzr4-d7q-1b091b58,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D7Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '1b091b58-9aa0-4c52-9aa3-d2a571317f88', 'content': {'TimeStamp': '2013-05-15 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.282796, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.516, 'OriginalWidth': 10.081, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:18:50.363842,\n", + " \t\t\tupdated_at: 2024-05-06T15:18:50.363842\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 672ecbda-47a2-4ec7-88af-c149eaceb8df,\n", + " \t\t\tname: YS2-FzR4-S1Q,\n", + " \t\t\tslug: ys2-fzr4-s1q-672ecbda,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '672ecbda-47a2-4ec7-88af-c149eaceb8df', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.490034999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.065, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:20:00.154967,\n", + " \t\t\tupdated_at: 2024-05-06T15:20:00.154967\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: c9c465e0-0e0b-4fca-8e05-e8750a8bbe82,\n", + " \t\t\tname: YS2-FzR4-S2Q,\n", + " \t\t\tslug: ys2-fzr4-s2q-c9c465e0,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'c9c465e0-0e0b-4fca-8e05-e8750a8bbe82', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.408183, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.533, 'OriginalWidth': 10.051, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:20:34.725996,\n", + " \t\t\tupdated_at: 2024-05-06T15:20:34.725996\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 495e379c-1966-4055-8b9f-aa9070de88ef,\n", + " \t\t\tname: YS2-FzR4-S3Q,\n", + " \t\t\tslug: ys2-fzr4-s3q-495e379c,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '495e379c-1966-4055-8b9f-aa9070de88ef', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.448482, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.038, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:21:06.436822,\n", + " \t\t\tupdated_at: 2024-05-06T15:21:06.436822\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 8f9ffd25-f337-422d-a7ad-964c0fe48a97,\n", + " \t\t\tname: YS1-Sz0°-S1L,\n", + " \t\t\tslug: ys1-sz0-s1l-8f9ffd25,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S1L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '8f9ffd25-f337-422d-a7ad-964c0fe48a97', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.548296, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1L', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:21:39.354093,\n", + " \t\t\tupdated_at: 2024-05-06T15:21:39.354093\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 7eb23c15-88f2-42c9-8b78-a406733495fb,\n", + " \t\t\tname: YS1-Sz0°-S1Q,\n", + " \t\t\tslug: ys1-sz0-s1q-7eb23c15,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '7eb23c15-88f2-42c9-8b78-a406733495fb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.007, 'OriginalCrosssection': 10.475465, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1Q', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:22:08.507191,\n", + " \t\t\tupdated_at: 2024-05-06T15:22:08.507191\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 9206d49b-71e9-4b51-80d2-cbd67f03cbbd,\n", + " \t\t\tname: YS1-Sz0°-S2L,\n", + " \t\t\tslug: ys1-sz0-s2l-9206d49b,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S2L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '9206d49b-71e9-4b51-80d2-cbd67f03cbbd', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.517325000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:22:38.075277,\n", + " \t\t\tupdated_at: 2024-05-06T15:22:38.075277\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 4471f17e-3f9c-4f70-b9dc-cf1c6894a40b,\n", + " \t\t\tname: YS1-Sz0°-S2Q,\n", + " \t\t\tslug: ys1-sz0-s2q-4471f17e,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '4471f17e-3f9c-4f70-b9dc-cf1c6894a40b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.01, 'OriginalCrosssection': 10.47294, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2Q', 'OriginalThickness': 1.494, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:23:07.127597,\n", + " \t\t\tupdated_at: 2024-05-06T15:23:07.127597\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: e9f8df8f-201f-440d-9971-1634cb56bdbb,\n", + " \t\t\tname: YS1-Sz0°-S3L,\n", + " \t\t\tslug: ys1-sz0-s3l-e9f8df8f,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S3L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'e9f8df8f-201f-440d-9971-1634cb56bdbb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.043, 'OriginalCrosssection': 10.529285000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:23:36.615187,\n", + " \t\t\tupdated_at: 2024-05-06T15:23:36.615187\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: e05b3406-84c3-4ce8-ae95-5678d95e9e20,\n", + " \t\t\tname: YS1-Sz0°-S3Q,\n", + " \t\t\tslug: ys1-sz0-s3q-e05b3406,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'e05b3406-84c3-4ce8-ae95-5678d95e9e20', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100025.0, 'Radius': 2.0, 'ShearLength': 7.019, 'OriginalCrosssection': 10.479367000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3Q', 'OriginalThickness': 1.493, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:24:05.800555,\n", + " \t\t\tupdated_at: 2024-05-06T15:24:05.800555\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 994550a4-0649-4d66-b6bb-ddce7298b338,\n", + " \t\t\tname: YS1-Sz0°-S4Q,\n", + " \t\t\tslug: ys1-sz0-s4q-994550a4,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S4Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '994550a4-0649-4d66-b6bb-ddce7298b338', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.515384, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S4Q', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:24:35.125022,\n", + " \t\t\tupdated_at: 2024-05-06T15:24:35.125022\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 89eea4e9-4286-4a30-b41f-572841f4e10b,\n", + " \t\t\tname: YS2-Sz0°-S1L,\n", + " \t\t\tslug: ys2-sz0-s1l-89eea4e9,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S1L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '89eea4e9-4286-4a30-b41f-572841f4e10b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.618806000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1L', 'OriginalThickness': 1.506, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:25:04.777217,\n", + " \t\t\tupdated_at: 2024-05-06T15:25:04.777217\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: e26d2a56-5782-4605-9b14-46d5a809b5a9,\n", + " \t\t\tname: YS2-Sz0°-S1Q,\n", + " \t\t\tslug: ys2-sz0-s1q-e26d2a56,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'e26d2a56-5782-4605-9b14-46d5a809b5a9', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.032, 'OriginalCrosssection': 10.625352, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1Q', 'OriginalThickness': 1.511, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:25:34.941942,\n", + " \t\t\tupdated_at: 2024-05-06T15:25:34.941942\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 6acadb27-9804-47f5-9a59-affe2ba82e77,\n", + " \t\t\tname: YS2-Sz0°-S2L,\n", + " \t\t\tslug: ys2-sz0-s2l-6acadb27,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S2L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '6acadb27-9804-47f5-9a59-affe2ba82e77', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.036, 'OriginalCrosssection': 10.568071999999999, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2L', 'OriginalThickness': 1.502, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:26:05.507889,\n", + " \t\t\tupdated_at: 2024-05-06T15:26:05.507889\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 829c4272-64dd-4089-8aab-94fda9b9aec5,\n", + " \t\t\tname: YS2-Sz0°-S2Q,\n", + " \t\t\tslug: ys2-sz0-s2q-829c4272,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '829c4272-64dd-4089-8aab-94fda9b9aec5', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.634877, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2Q', 'OriginalThickness': 1.513, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:26:35.271807,\n", + " \t\t\tupdated_at: 2024-05-06T15:26:35.271807\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 5069d37a-4ede-42b0-bf58-542581e37f84,\n", + " \t\t\tname: YS2-Sz0°-S3L,\n", + " \t\t\tslug: ys2-sz0-s3l-5069d37a,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S3L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '5069d37a-4ede-42b0-bf58-542581e37f84', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.049, 'OriginalCrosssection': 10.580549, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3L', 'OriginalThickness': 1.501, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:27:05.317794,\n", + " \t\t\tupdated_at: 2024-05-06T15:27:05.317794\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b0e5f8b6-192e-4713-96ca-6a023cf3af76,\n", + " \t\t\tname: YS2-Sz0°-S3Q,\n", + " \t\t\tslug: ys2-sz0-s3q-b0e5f8b6,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'b0e5f8b6-192e-4713-96ca-6a023cf3af76', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.65099, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3Q', 'OriginalThickness': 1.514, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:27:35.118023,\n", + " \t\t\tupdated_at: 2024-05-06T15:27:35.118023\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -919,9 +2090,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-15 08:41:32.727310, \n", + " \tcreated_at = 2024-05-06 16:53:59.576957, \n", " \n", - " \tupdated_at = 2024-03-15 08:41:32.727310, \n", + " \tupdated_at = 2024-05-06 16:53:59.576957, \n", " \n", " \texternal_links = [], \n", " \n", @@ -941,13 +2112,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175, \n", + " \tid = e3487b03-e96a-49a4-bcb1-6599802ada8f, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-b4bc4f7e, \n", + " \tslug = foo1-e3487b03, \n", " \n", " \tannotations = [], \n", " \n", @@ -955,7 +2126,31 @@ " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: 843e49e2-ae33-4534-b0a8-5b93323b62a2\n", + " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f,\n", + " \t\t\tname: foo 2,\n", + " \t\t\tslug: foo2-3975cd66,\n", + " \t\t\tktype_id: organization,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:52:41.117372,\n", + " \t\t\tupdated_at: 2024-05-13T11:52:41.117372\n", " \t\t}\n", " \t], \n", " \n", @@ -963,7 +2158,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -971,9 +2166,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:45.004142, \n", + " \tcreated_at = 2024-05-13 11:52:40.683110, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:45.004142, \n", + " \tupdated_at = 2024-05-13 11:52:40.683110, \n", " \n", " \texternal_links = [], \n", " \n", @@ -988,34 +2183,127 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", + " ), fuzzy=False)]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` and `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SearchResult(hit=KItem(\n", " \n", - " \tname = csv_test, \n", + " \tname = 3D-Blechmodelle2, \n", " \n", - " \tid = 99bbb5d3-add5-42b0-9b75-93ee29b01766, \n", + " \tid = 493a9075-c30c-48c2-b9d6-2da408f0ecda, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = csvtest, \n", + " \tslug = 3d-blechmodelle2-493a9075, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [\n", " \t\t{\n", + " \t\t\tid: aaabc3d4-d06e-4512-9fe3-0d01f815690e,\n", + " \t\t\tname: DX56_D_FZ2_WR00_43,\n", + " \t\t\tslug: dx56_d_fz2_wr00_43-aaabc3d4,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DX56D,\n", + " \t\t\tname: DX56D,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", + " \t\t\tname: TensileTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", + " \t\t}, {\n", + " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'aaabc3d4-d06e-4512-9fe3-0d01f815690e', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DX56D', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'wes', 'SampleIdentifier-2': 'DX56_D_FZ2_WR00_43', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.55, 'SpecimenWidth': 20.04, 'TestingRate': 0.1, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", + " \t\t\tcreated_at: 2024-05-06T15:03:11.134388,\n", + " \t\t\tupdated_at: 2024-05-06T15:03:11.134388\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 57fe5c58-38ab-4c8a-8f82-efce9422c291,\n", + " \t\t\tname: DP800_F_FZ2_WR15_97,\n", + " \t\t\tslug: dp800_f_fz2_wr15_97-57fe5c58,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DP800,\n", + " \t\t\tname: DP800,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", + " \t\t\tname: TensileTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", + " \t\t}, {\n", + " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: DP800_F_FZ2_WR15_97.csv\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '57fe5c58-38ab-4c8a-8f82-efce9422c291', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DP800', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'fuchs', 'SampleIdentifier-2': 'DP800_F_FZ2_WR15_97', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.513, 'SpecimenWidth': 20.015, 'TestingRate': 0.24, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", + " \t\t\tcreated_at: 2024-05-06T15:03:46.647966,\n", + " \t\t\tupdated_at: 2024-05-06T15:03:46.647966\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1023,9 +2311,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:32:06.073842, \n", + " \tcreated_at = 2024-05-06 16:53:49.061347, \n", " \n", - " \tupdated_at = 2024-04-16 18:32:06.073842, \n", + " \tupdated_at = 2024-05-06 16:53:49.061347, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1043,31 +2331,55 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = test, \n", + " \tname = foo 1, \n", " \n", - " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", + " \tid = c60cca3e-1d14-4e8e-ad2d-92bda7f975e5, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = test, \n", + " \tslug = foo1-c60cca3e, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe,\n", + " \t\t\tname: foo 2,\n", + " \t\t\tslug: foo2-56791862,\n", + " \t\t\tktype_id: organization,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:49:54.440708,\n", + " \t\t\tupdated_at: 2024-05-13T11:49:54.440708\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1075,9 +2387,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:33:54.428378, \n", + " \tcreated_at = 2024-05-13 11:49:53.852559, \n", " \n", - " \tupdated_at = 2024-04-16 18:33:54.428378, \n", + " \tupdated_at = 2024-05-13 11:49:53.852559, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1095,27 +2407,57 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = INSTABILITAET, \n", + " \tname = foo 2, \n", " \n", - " \tid = eff582e8-7117-4b84-81dd-fafd0845bcae, \n", + " \tid = 56791862-dd99-4fdb-984a-f9741c7cfdfe, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = instabilitaet, \n", + " \tslug = foo2-56791862, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5,\n", + " \t\t\tname: foo 1,\n", + " \t\t\tslug: foo1-c60cca3e,\n", + " \t\t\tktype_id: dataset-catalog,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:49:53.852559,\n", + " \t\t\tupdated_at: 2024-05-13T11:49:53.852559\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1123,15 +2465,15 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 21:53:04.128883, \n", + " \tcreated_at = 2024-05-13 11:49:54.440708, \n", " \n", - " \tupdated_at = 2024-04-23 21:53:04.128883, \n", + " \tupdated_at = 2024-05-13 11:49:54.440708, \n", " \n", " \texternal_links = [], \n", " \n", " \tkitem_apps = [], \n", " \n", - " \tsummary = Summary(text=INSTABILITAET Project at Fraunhofer IWM), \n", + " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", @@ -1143,15 +2485,15 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = aaaaaa, \n", + " \tname = foo 3, \n", " \n", - " \tid = 6afc62f5-0da2-418f-9372-ae030a3e5e34, \n", + " \tid = 4eb3de3b-0d7a-4d5e-b6f6-60c40a746802, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = aaaaaa, \n", + " \tslug = foo3-4eb3de3b, \n", " \n", " \tannotations = [], \n", " \n", @@ -1163,7 +2505,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1171,9 +2513,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-26 08:12:27.156269, \n", + " \tcreated_at = 2024-05-13 11:49:54.988706, \n", " \n", - " \tupdated_at = 2024-03-26 08:12:27.156269, \n", + " \tupdated_at = 2024-05-13 11:49:54.988706, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1191,17 +2533,23 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = new1, \n", + " \tname = foo 4, \n", " \n", - " \tid = 47950cae-fd39-430c-8bb5-3f894a032d77, \n", + " \tid = 9d541ea2-deb6-42e6-bd91-5778848e361e, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = new1, \n", + " \tslug = foo4-9d541ea2, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", @@ -1211,7 +2559,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1219,9 +2567,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:36:49.861912, \n", + " \tcreated_at = 2024-05-13 11:49:55.546017, \n", " \n", - " \tupdated_at = 2024-04-16 18:36:49.861912, \n", + " \tupdated_at = 2024-05-13 11:49:55.546017, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1236,58 +2584,1108 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False)]" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... and for all of type `Organization` and `DatasetCatalog`:" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[SearchResult(hit=KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = Fraunhofer, \n", + " \tname = Crashartig, \n", " \n", - " \tid = 6c695c8b-06ec-4df8-b840-d2dc0752e22d, \n", + " \tid = 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad, \n", " \n", - " \tktype_id = organization, \n", + " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = fraunhofer, \n", + " \tslug = crashartig-6d09ce07, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/characterization-method#ElectricalCharacterisationMethods,\n", - " \t\t\tname: ElectricalCharacterisationMethods,\n", - " \t\t\tnamespace: method\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: 3352c236-b925-4f3a-86d3-87145f27834b\n", + " \t\t\tid: 84c23b6a-508a-4148-a224-fcefce65510e,\n", + " \t\t\tname: YS1-FzR4-D1Q,\n", + " \t\t\tslug: ys1-fzr4-d1q-84c23b6a,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '84c23b6a-508a-4148-a224-fcefce65510e', 'content': {'TimeStamp': '2013-06-03 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.083616, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.029, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:04:21.918404,\n", + " \t\t\tupdated_at: 2024-05-06T15:04:21.918404\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 11f2533e-f999-4151-85e6-e4821ce94c7e,\n", + " \t\t\tname: YS1-FzR4-D2Q,\n", + " \t\t\tslug: ys1-fzr4-d2q-11f2533e,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '11f2533e-f999-4151-85e6-e4821ce94c7e', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.044512000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.003, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:05:34.366982,\n", + " \t\t\tupdated_at: 2024-05-06T15:05:34.366982\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 19422f63-1087-47e4-9476-5def1ad264a4,\n", + " \t\t\tname: YS1-FzR4-D3Q,\n", + " \t\t\tslug: ys1-fzr4-d3q-19422f63,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '19422f63-1087-47e4-9476-5def1ad264a4', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.060554, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.502, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:06:47.134938,\n", + " \t\t\tupdated_at: 2024-05-06T15:06:47.134938\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 37989929-f27f-466a-b06b-55777cfa9c21,\n", + " \t\t\tname: YS1-FzR4-D5Q,\n", + " \t\t\tslug: ys1-fzr4-d5q-37989929,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D5Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '37989929-f27f-466a-b06b-55777cfa9c21', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.082055999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.507, 'OriginalWidth': 10.008, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:07:57.132627,\n", + " \t\t\tupdated_at: 2024-05-06T15:07:57.132627\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 2d31e9eb-3b88-497b-a69c-1dca43678841,\n", + " \t\t\tname: YS1-FzR4-D6Q,\n", + " \t\t\tslug: ys1-fzr4-d6q-2d31e9eb,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D6Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '2d31e9eb-3b88-497b-a69c-1dca43678841', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.080607999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D6L', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:09:05.808242,\n", + " \t\t\tupdated_at: 2024-05-06T15:09:05.808242\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 6ebe0e55-3904-440b-98c0-7d91fac824a9,\n", + " \t\t\tname: YS1-FzR4-D7Q,\n", + " \t\t\tslug: ys1-fzr4-d7q-6ebe0e55,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", + " \t\t\tname: ZStE340,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-D7Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '6ebe0e55-3904-440b-98c0-7d91fac824a9', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.028012, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.501, 'OriginalWidth': 10.012, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:10:13.892563,\n", + " \t\t\tupdated_at: 2024-05-06T15:10:13.892563\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 4697d3ff-dbb1-4574-9b30-a8bb28ba9fee,\n", + " \t\t\tname: YS1-FzR4-S1Q,\n", + " \t\t\tslug: ys1-fzr4-s1q-4697d3ff,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", + " \t\t\tname: H340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '4697d3ff-dbb1-4574-9b30-a8bb28ba9fee', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.04996, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.04, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:11:21.835900,\n", + " \t\t\tupdated_at: 2024-05-06T15:11:21.835900\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b51b724c-4030-4767-9b2e-76b5548c078f,\n", + " \t\t\tname: YS1-FzR4-S2Q,\n", + " \t\t\tslug: ys1-fzr4-s2q-b51b724c,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", + " \t\t\tname: H340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'b51b724c-4030-4767-9b2e-76b5548c078f', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.027936, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.498, 'OriginalWidth': 10.032, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:11:54.654278,\n", + " \t\t\tupdated_at: 2024-05-06T15:11:54.654278\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 12bed326-a605-46f4-82f3-ae980a7a0548,\n", + " \t\t\tname: YS1-FzR4-S3Q,\n", + " \t\t\tslug: ys1-fzr4-s3q-12bed326,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", + " \t\t\tname: H340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-FzR4-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '12bed326-a605-46f4-82f3-ae980a7a0548', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.025976, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.024, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:12:27.310845,\n", + " \t\t\tupdated_at: 2024-05-06T15:12:27.310845\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 31566fde-4356-4c3f-ae2a-63c1a7e8da0d,\n", + " \t\t\tname: YS2-FzR4-D1Q,\n", + " \t\t\tslug: ys2-fzr4-d1q-31566fde,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '31566fde-4356-4c3f-ae2a-63c1a7e8da0d', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.397029999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.529, 'OriginalWidth': 10.07, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:12:59.925719,\n", + " \t\t\tupdated_at: 2024-05-06T15:12:59.925719\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 13c64610-8251-443e-9560-4d2297a10543,\n", + " \t\t\tname: YS2-FzR4-D2Q,\n", + " \t\t\tslug: ys2-fzr4-d2q-13c64610,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '13c64610-8251-443e-9560-4d2297a10543', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.316811, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.523, 'OriginalWidth': 10.057, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:14:10.649454,\n", + " \t\t\tupdated_at: 2024-05-06T15:14:10.649454\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b704fa17-83f4-434d-8807-14b201f35659,\n", + " \t\t\tname: YS2-FzR4-D3Q,\n", + " \t\t\tslug: ys2-fzr4-d3q-b704fa17,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'b704fa17-83f4-434d-8807-14b201f35659', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.366201, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.527, 'OriginalWidth': 10.063, 'CrossheadSeparationRate': 25.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:15:23.600966,\n", + " \t\t\tupdated_at: 2024-05-06T15:15:23.600966\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 7dd2b925-7bf7-4cc0-9a78-aac7342aa283,\n", + " \t\t\tname: YS2-FzR4-D5Q,\n", + " \t\t\tslug: ys2-fzr4-d5q-7dd2b925,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D5Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '7dd2b925-7bf7-4cc0-9a78-aac7342aa283', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.351816000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.528, 'OriginalWidth': 10.047, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:16:32.952408,\n", + " \t\t\tupdated_at: 2024-05-06T15:16:32.952408\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 14b75028-fb80-4e11-8419-b4f00dd365fa,\n", + " \t\t\tname: YS2-FzR4-D6Q,\n", + " \t\t\tslug: ys2-fzr4-d6q-14b75028,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D6Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '14b75028-fb80-4e11-8419-b4f00dd365fa', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.38894, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D6Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.532, 'OriginalWidth': 10.045, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:17:43.978073,\n", + " \t\t\tupdated_at: 2024-05-06T15:17:43.978073\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 1b091b58-9aa0-4c52-9aa3-d2a571317f88,\n", + " \t\t\tname: YS2-FzR4-D7Q,\n", + " \t\t\tslug: ys2-fzr4-d7q-1b091b58,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-D7Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '1b091b58-9aa0-4c52-9aa3-d2a571317f88', 'content': {'TimeStamp': '2013-05-15 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.282796, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.516, 'OriginalWidth': 10.081, 'CrossheadSeparationRate': 2500.0}},\n", + " \t\t\tcreated_at: 2024-05-06T15:18:50.363842,\n", + " \t\t\tupdated_at: 2024-05-06T15:18:50.363842\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 672ecbda-47a2-4ec7-88af-c149eaceb8df,\n", + " \t\t\tname: YS2-FzR4-S1Q,\n", + " \t\t\tslug: ys2-fzr4-s1q-672ecbda,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '672ecbda-47a2-4ec7-88af-c149eaceb8df', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.490034999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.065, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:20:00.154967,\n", + " \t\t\tupdated_at: 2024-05-06T15:20:00.154967\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: c9c465e0-0e0b-4fca-8e05-e8750a8bbe82,\n", + " \t\t\tname: YS2-FzR4-S2Q,\n", + " \t\t\tslug: ys2-fzr4-s2q-c9c465e0,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'c9c465e0-0e0b-4fca-8e05-e8750a8bbe82', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.408183, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.533, 'OriginalWidth': 10.051, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:20:34.725996,\n", + " \t\t\tupdated_at: 2024-05-06T15:20:34.725996\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 495e379c-1966-4055-8b9f-aa9070de88ef,\n", + " \t\t\tname: YS2-FzR4-S3Q,\n", + " \t\t\tslug: ys2-fzr4-s3q-495e379c,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", + " \t\t\tname: NotchTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", + " \t\t\tname: HXT980X,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-FzR4-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '495e379c-1966-4055-8b9f-aa9070de88ef', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.448482, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.038, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:21:06.436822,\n", + " \t\t\tupdated_at: 2024-05-06T15:21:06.436822\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 8f9ffd25-f337-422d-a7ad-964c0fe48a97,\n", + " \t\t\tname: YS1-Sz0°-S1L,\n", + " \t\t\tslug: ys1-sz0-s1l-8f9ffd25,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S1L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '8f9ffd25-f337-422d-a7ad-964c0fe48a97', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.548296, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1L', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:21:39.354093,\n", + " \t\t\tupdated_at: 2024-05-06T15:21:39.354093\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 7eb23c15-88f2-42c9-8b78-a406733495fb,\n", + " \t\t\tname: YS1-Sz0°-S1Q,\n", + " \t\t\tslug: ys1-sz0-s1q-7eb23c15,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '7eb23c15-88f2-42c9-8b78-a406733495fb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.007, 'OriginalCrosssection': 10.475465, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1Q', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:22:08.507191,\n", + " \t\t\tupdated_at: 2024-05-06T15:22:08.507191\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 9206d49b-71e9-4b51-80d2-cbd67f03cbbd,\n", + " \t\t\tname: YS1-Sz0°-S2L,\n", + " \t\t\tslug: ys1-sz0-s2l-9206d49b,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S2L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '9206d49b-71e9-4b51-80d2-cbd67f03cbbd', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.517325000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:22:38.075277,\n", + " \t\t\tupdated_at: 2024-05-06T15:22:38.075277\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 4471f17e-3f9c-4f70-b9dc-cf1c6894a40b,\n", + " \t\t\tname: YS1-Sz0°-S2Q,\n", + " \t\t\tslug: ys1-sz0-s2q-4471f17e,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '4471f17e-3f9c-4f70-b9dc-cf1c6894a40b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.01, 'OriginalCrosssection': 10.47294, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2Q', 'OriginalThickness': 1.494, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:23:07.127597,\n", + " \t\t\tupdated_at: 2024-05-06T15:23:07.127597\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: e9f8df8f-201f-440d-9971-1634cb56bdbb,\n", + " \t\t\tname: YS1-Sz0°-S3L,\n", + " \t\t\tslug: ys1-sz0-s3l-e9f8df8f,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S3L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'e9f8df8f-201f-440d-9971-1634cb56bdbb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.043, 'OriginalCrosssection': 10.529285000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:23:36.615187,\n", + " \t\t\tupdated_at: 2024-05-06T15:23:36.615187\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: e05b3406-84c3-4ce8-ae95-5678d95e9e20,\n", + " \t\t\tname: YS1-Sz0°-S3Q,\n", + " \t\t\tslug: ys1-sz0-s3q-e05b3406,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'e05b3406-84c3-4ce8-ae95-5678d95e9e20', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100025.0, 'Radius': 2.0, 'ShearLength': 7.019, 'OriginalCrosssection': 10.479367000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3Q', 'OriginalThickness': 1.493, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:24:05.800555,\n", + " \t\t\tupdated_at: 2024-05-06T15:24:05.800555\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 994550a4-0649-4d66-b6bb-ddce7298b338,\n", + " \t\t\tname: YS1-Sz0°-S4Q,\n", + " \t\t\tslug: ys1-sz0-s4q-994550a4,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", + " \t\t\tname: HX340LAD,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS1-Sz0-S4Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '994550a4-0649-4d66-b6bb-ddce7298b338', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.515384, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S4Q', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:24:35.125022,\n", + " \t\t\tupdated_at: 2024-05-06T15:24:35.125022\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 89eea4e9-4286-4a30-b41f-572841f4e10b,\n", + " \t\t\tname: YS2-Sz0°-S1L,\n", + " \t\t\tslug: ys2-sz0-s1l-89eea4e9,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S1L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '89eea4e9-4286-4a30-b41f-572841f4e10b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.618806000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1L', 'OriginalThickness': 1.506, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:25:04.777217,\n", + " \t\t\tupdated_at: 2024-05-06T15:25:04.777217\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: e26d2a56-5782-4605-9b14-46d5a809b5a9,\n", + " \t\t\tname: YS2-Sz0°-S1Q,\n", + " \t\t\tslug: ys2-sz0-s1q-e26d2a56,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S1Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'e26d2a56-5782-4605-9b14-46d5a809b5a9', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.032, 'OriginalCrosssection': 10.625352, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1Q', 'OriginalThickness': 1.511, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:25:34.941942,\n", + " \t\t\tupdated_at: 2024-05-06T15:25:34.941942\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 6acadb27-9804-47f5-9a59-affe2ba82e77,\n", + " \t\t\tname: YS2-Sz0°-S2L,\n", + " \t\t\tslug: ys2-sz0-s2l-6acadb27,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S2L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '6acadb27-9804-47f5-9a59-affe2ba82e77', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.036, 'OriginalCrosssection': 10.568071999999999, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2L', 'OriginalThickness': 1.502, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:26:05.507889,\n", + " \t\t\tupdated_at: 2024-05-06T15:26:05.507889\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 829c4272-64dd-4089-8aab-94fda9b9aec5,\n", + " \t\t\tname: YS2-Sz0°-S2Q,\n", + " \t\t\tslug: ys2-sz0-s2q-829c4272,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S2Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '829c4272-64dd-4089-8aab-94fda9b9aec5', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.634877, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2Q', 'OriginalThickness': 1.513, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:26:35.271807,\n", + " \t\t\tupdated_at: 2024-05-06T15:26:35.271807\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: 5069d37a-4ede-42b0-bf58-542581e37f84,\n", + " \t\t\tname: YS2-Sz0°-S3L,\n", + " \t\t\tslug: ys2-sz0-s3l-5069d37a,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S3L.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': '5069d37a-4ede-42b0-bf58-542581e37f84', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.049, 'OriginalCrosssection': 10.580549, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3L', 'OriginalThickness': 1.501, 'CrossheadSeparationRate': 0.01}},\n", + " \t\t\tcreated_at: 2024-05-06T15:27:05.317794,\n", + " \t\t\tupdated_at: 2024-05-06T15:27:05.317794\n", + " \t\t}, \n", + " \t\t{\n", + " \t\t\tid: b0e5f8b6-192e-4713-96ca-6a023cf3af76,\n", + " \t\t\tname: YS2-Sz0°-S3Q,\n", + " \t\t\tslug: ys2-sz0-s3q-b0e5f8b6,\n", + " \t\t\tktype_id: dataset,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", + " \t\t\tname: ShearTest,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}, {\n", + " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", + " \t\t\tname: HCT980X+Z110MB,\n", + " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", + " \t\t}, {\n", + " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [{\n", + " \t\t\tname: YS2-Sz0-S3Q.xlsx\n", + " \t\t}],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: {'id': 'b0e5f8b6-192e-4713-96ca-6a023cf3af76', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.65099, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3Q', 'OriginalThickness': 1.514, 'CrossheadSeparationRate': 0.02}},\n", + " \t\t\tcreated_at: 2024-05-06T15:27:35.118023,\n", + " \t\t\tupdated_at: 2024-05-06T15:27:35.118023\n", " \t\t}\n", " \t], \n", " \n", @@ -1295,17 +3693,17 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 8d01f084-3604-4956-bd42-a09610e71d0e\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", - " \tavatar_exists = True, \n", + " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-13 15:20:23.000643, \n", + " \tcreated_at = 2024-05-06 16:53:59.576957, \n", " \n", - " \tupdated_at = 2024-02-13 15:20:23.000643, \n", + " \tupdated_at = 2024-05-06 16:53:59.576957, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1323,31 +3721,55 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = EBSD_HX340LAD, \n", + " \tname = foo 1, \n", " \n", - " \tid = e0bda0c3-d136-4a0c-b637-ab582b9ceea6, \n", + " \tid = e3487b03-e96a-49a4-bcb1-6599802ada8f, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = ebsdhx340lad, \n", + " \tslug = foo1-e3487b03, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tname: 11533_FS_Scan1_830x830_100x.ang\n", + " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f,\n", + " \t\t\tname: foo 2,\n", + " \t\t\tslug: foo2-3975cd66,\n", + " \t\t\tktype_id: organization,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:52:41.117372,\n", + " \t\t\tupdated_at: 2024-05-13T11:52:41.117372\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1355,9 +3777,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-20 14:41:23.907652, \n", + " \tcreated_at = 2024-05-13 11:52:40.683110, \n", " \n", - " \tupdated_at = 2024-02-20 14:41:23.907652, \n", + " \tupdated_at = 2024-05-13 11:52:40.683110, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1375,33 +3797,57 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = testtest, \n", + " \tname = foo 2, \n", " \n", - " \tid = 0f995978-9932-436a-9d76-961463457ed2, \n", + " \tid = 3975cd66-270a-4680-bfa6-07edd469295f, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = testtest, \n", + " \tslug = foo2-3975cd66, \n", " \n", " \tannotations = [\n", " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/material#SinglePhaseAlloy,\n", - " \t\t\tname: SinglePhaseAlloy,\n", - " \t\t\tnamespace: material\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", " \t\t}\n", " \t], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", + " \tlinked_kitems = [\n", + " \t\t{\n", + " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f,\n", + " \t\t\tname: foo 1,\n", + " \t\t\tslug: foo1-e3487b03,\n", + " \t\t\tktype_id: dataset-catalog,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:52:40.683110,\n", + " \t\t\tupdated_at: 2024-05-13T11:52:40.683110\n", + " \t\t}\n", + " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: de5e5310-ae4b-4383-b1d5-1fc6cbbc5f2f\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1409,9 +3855,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-14 08:44:46.504818, \n", + " \tcreated_at = 2024-05-13 11:52:41.117372, \n", " \n", - " \tupdated_at = 2024-03-14 08:44:46.504818, \n", + " \tupdated_at = 2024-05-13 11:52:41.117372, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1429,15 +3875,15 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = my_tensiletest, \n", + " \tname = foo 3, \n", " \n", - " \tid = e0b09fe1-5425-4053-abc3-86e7e9e49b25, \n", + " \tid = b1a05898-cf64-4dc2-85c1-ac9902cc48cf, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = mytensiletest, \n", + " \tslug = foo3-b1a05898, \n", " \n", " \tannotations = [], \n", " \n", @@ -1449,7 +3895,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 6377ec8b-fbe6-4997-a8c6-53acecd86470\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1457,9 +3903,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-03-15 08:41:32.727310, \n", + " \tcreated_at = 2024-05-13 11:52:41.529938, \n", " \n", - " \tupdated_at = 2024-03-15 08:41:32.727310, \n", + " \tupdated_at = 2024-05-13 11:52:41.529938, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1477,31 +3923,33 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = foo 4, \n", " \n", - " \tid = b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175, \n", + " \tid = 997d8c3d-5ebf-466b-9f9c-35fd13875260, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-b4bc4f7e, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", + " \tslug = foo4-997d8c3d, \n", " \n", - " \tlinked_kitems = [\n", + " \tannotations = [\n", " \t\t{\n", - " \t\t\tid: 843e49e2-ae33-4534-b0a8-5b93323b62a2\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: www.example.org\n", " \t\t}\n", " \t], \n", " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1509,9 +3957,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:45.004142, \n", + " \tcreated_at = 2024-05-13 11:52:41.967220, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:45.004142, \n", + " \tupdated_at = 2024-05-13 11:52:41.967220, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1526,142 +3974,84 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 2, \n", - " \n", - " \tid = 843e49e2-ae33-4534-b0a8-5b93323b62a2, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo2-843e49e2, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-23 23:46:45.577428, \n", - " \n", - " \tupdated_at = 2024-04-23 23:46:45.577428, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", + " ), fuzzy=False)]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... or for all of type `DatasetCatalog` with `foo` in the name:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SearchResult(hit=KItem(\n", " \n", - " \tname = foo 3, \n", + " \tname = foo 1, \n", " \n", - " \tid = 33c20a1f-5429-404b-bdc6-3868fd4675e5, \n", + " \tid = c60cca3e-1d14-4e8e-ad2d-92bda7f975e5, \n", " \n", - " \tktype_id = organization, \n", + " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo3-33c20a1f, \n", + " \tslug = foo1-c60cca3e, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-23 23:46:46.185321, \n", - " \n", - " \tupdated_at = 2024-04-23 23:46:46.185321, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 4, \n", - " \n", - " \tid = ee5cc269-ef4a-4ff2-b704-60aa66af4b4a, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo4-ee5cc269, \n", - " \n", - " \tannotations = [\n", + " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tiri: www.example.org/bar,\n", - " \t\t\tname: bar,\n", + " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe,\n", + " \t\t\tname: foo 2,\n", + " \t\t\tslug: foo2-56791862,\n", + " \t\t\tktype_id: organization,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", " \t\t\tnamespace: www.example.org\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:49:54.440708,\n", + " \t\t\tupdated_at: 2024-05-13T11:49:54.440708\n", " \t\t}\n", " \t], \n", " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1669,9 +4059,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:46.768925, \n", + " \tcreated_at = 2024-05-13 11:49:53.852559, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:46.768925, \n", + " \tupdated_at = 2024-05-13 11:49:53.852559, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1689,31 +4079,55 @@ " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = csv_test, \n", + " \tname = foo 1, \n", " \n", - " \tid = 99bbb5d3-add5-42b0-9b75-93ee29b01766, \n", + " \tid = e3487b03-e96a-49a4-bcb1-6599802ada8f, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = csvtest, \n", + " \tslug = foo1-e3487b03, \n", " \n", " \tannotations = [], \n", " \n", - " \tattachments = [\n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f,\n", + " \t\t\tname: foo 2,\n", + " \t\t\tslug: foo2-3975cd66,\n", + " \t\t\tktype_id: organization,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:52:41.117372,\n", + " \t\t\tupdated_at: 2024-05-13T11:52:41.117372\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1721,9 +4135,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-16 18:32:06.073842, \n", + " \tcreated_at = 2024-05-13 11:52:40.683110, \n", " \n", - " \tupdated_at = 2024-04-16 18:32:06.073842, \n", + " \tupdated_at = 2024-05-13 11:52:40.683110, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1738,82 +4152,86 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", + " ), fuzzy=False)]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` with the annotation `www.example.org/foo`:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SearchResult(hit=KItem(\n", " \n", - " \tname = test, \n", + " \tname = foo 2, \n", " \n", - " \tid = 77b51a68-764f-467c-ae78-a2b543dc8eba, \n", + " \tid = 56791862-dd99-4fdb-984a-f9741c7cfdfe, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = test, \n", + " \tslug = foo2-56791862, \n", " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [\n", + " \tannotations = [\n", " \t\t{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", " \t\t}\n", " \t], \n", " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", + " \tattachments = [], \n", " \n", - " \tauthors = [\n", + " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5,\n", + " \t\t\tname: foo 1,\n", + " \t\t\tslug: foo1-c60cca3e,\n", + " \t\t\tktype_id: dataset-catalog,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:49:53.852559,\n", + " \t\t\tupdated_at: 2024-05-13T11:49:53.852559\n", " \t\t}\n", " \t], \n", " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-16 18:33:54.428378, \n", - " \n", - " \tupdated_at = 2024-04-16 18:33:54.428378, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = INSTABILITAET, \n", - " \n", - " \tid = eff582e8-7117-4b84-81dd-fafd0845bcae, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = instabilitaet, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1821,15 +4239,15 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 21:53:04.128883, \n", + " \tcreated_at = 2024-05-13 11:49:54.440708, \n", " \n", - " \tupdated_at = 2024-04-23 21:53:04.128883, \n", + " \tupdated_at = 2024-05-13 11:49:54.440708, \n", " \n", " \texternal_links = [], \n", " \n", " \tkitem_apps = [], \n", " \n", - " \tsummary = Summary(text=INSTABILITAET Project at Fraunhofer IWM), \n", + " \tsummary = None, \n", " \n", " \tuser_groups = [], \n", " \n", @@ -1838,100 +4256,52 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=207.91),\n", + " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = new1, \n", + " \tname = foo 2, \n", " \n", - " \tid = 47950cae-fd39-430c-8bb5-3f894a032d77, \n", + " \tid = 3975cd66-270a-4680-bfa6-07edd469295f, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = new1, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", + " \tslug = foo2-3975cd66, \n", " \n", - " \tauthors = [\n", + " \tannotations = [\n", " \t\t{\n", - " \t\t\tuser_id: c61e1c33-1658-4199-a809-ff693684cc70\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org\n", " \t\t}\n", " \t], \n", " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-16 18:36:49.861912, \n", - " \n", - " \tupdated_at = 2024-04-16 18:36:49.861912, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=245.71)]" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... or for all of type `DatasetCatalog` with `foo` in the name:" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo1-b4bc4f7e, \n", - " \n", - " \tannotations = [], \n", - " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", " \t\t{\n", - " \t\t\tid: 843e49e2-ae33-4534-b0a8-5b93323b62a2\n", + " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f,\n", + " \t\t\tname: foo 1,\n", + " \t\t\tslug: foo1-e3487b03,\n", + " \t\t\tktype_id: dataset-catalog,\n", + " \t\t\tsummary: None,\n", + " \t\t\tavatar_exists: False,\n", + " \t\t\tannotations: [],\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f\n", + " \t\t}],\n", + " \t\t\texternal_links: [],\n", + " \t\t\tcontacts: [],\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t}],\n", + " \t\t\tlinked_affiliations: [],\n", + " \t\t\tattachments: [],\n", + " \t\t\tuser_groups: [],\n", + " \t\t\tcustom_properties: None,\n", + " \t\t\tcreated_at: 2024-05-13T11:52:40.683110,\n", + " \t\t\tupdated_at: 2024-05-13T11:52:40.683110\n", " \t\t}\n", " \t], \n", " \n", @@ -1939,7 +4309,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -1947,9 +4317,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:45.004142, \n", + " \tcreated_at = 2024-05-13 11:52:41.117372, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:45.004142, \n", + " \tupdated_at = 2024-05-13 11:52:41.117372, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1964,66 +4334,30 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False)]" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... and for all of type `Organization` with the annotation `www.example.org/foo`:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[SearchResult(hit=KItem(\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 2, \n", + " \tname = foo 3, \n", " \n", - " \tid = 843e49e2-ae33-4534-b0a8-5b93323b62a2, \n", + " \tid = 4eb3de3b-0d7a-4d5e-b6f6-60c40a746802, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo2-843e49e2, \n", + " \tslug = foo3-4eb3de3b, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: b4bc4f7e-cd6f-4d9d-b05c-38c058c2d175\n", - " \t\t}\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -2031,9 +4365,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:45.577428, \n", + " \tcreated_at = 2024-05-13 11:49:54.988706, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:45.577428, \n", + " \tupdated_at = 2024-05-13 11:49:54.988706, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2048,50 +4382,40 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False),\n", + " ), fuzzy=284.02),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = Fraunhofer, \n", + " \tname = foo 3, \n", " \n", - " \tid = 6c695c8b-06ec-4df8-b840-d2dc0752e22d, \n", + " \tid = b1a05898-cf64-4dc2-85c1-ac9902cc48cf, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = fraunhofer, \n", + " \tslug = foo3-b1a05898, \n", " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: http://www.oie.eu/ontology/characterization-method#ElectricalCharacterisationMethods,\n", - " \t\t\tname: ElectricalCharacterisationMethods,\n", - " \t\t\tnamespace: method\n", - " \t\t}\n", - " \t], \n", + " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 3352c236-b925-4f3a-86d3-87145f27834b\n", - " \t\t}\n", - " \t], \n", + " \tlinked_kitems = [], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 8d01f084-3604-4956-bd42-a09610e71d0e\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", - " \tavatar_exists = True, \n", + " \tavatar_exists = False, \n", " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-02-13 15:20:23.000643, \n", + " \tcreated_at = 2024-05-13 11:52:41.529938, \n", " \n", - " \tupdated_at = 2024-02-13 15:20:23.000643, \n", + " \tupdated_at = 2024-05-13 11:52:41.529938, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2106,20 +4430,26 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=237.27),\n", + " ), fuzzy=284.02),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 3, \n", + " \tname = foo 4, \n", " \n", - " \tid = 33c20a1f-5429-404b-bdc6-3868fd4675e5, \n", + " \tid = 9d541ea2-deb6-42e6-bd91-5778848e361e, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo3-33c20a1f, \n", + " \tslug = foo4-9d541ea2, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: www.example.org\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", @@ -2129,7 +4459,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -2137,9 +4467,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:46.185321, \n", + " \tcreated_at = 2024-05-13 11:49:55.546017, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:46.185321, \n", + " \tupdated_at = 2024-05-13 11:49:55.546017, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2154,18 +4484,18 @@ " \thdf5 = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=284.02),\n", + " ), fuzzy=284.24),\n", " SearchResult(hit=KItem(\n", " \n", " \tname = foo 4, \n", " \n", - " \tid = ee5cc269-ef4a-4ff2-b704-60aa66af4b4a, \n", + " \tid = 997d8c3d-5ebf-466b-9f9c-35fd13875260, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo4-ee5cc269, \n", + " \tslug = foo4-997d8c3d, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -2183,7 +4513,7 @@ " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 681614d0-9c59-4ee6-8dd5-409260ce0980\n", + " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", " \t\t}\n", " \t], \n", " \n", @@ -2191,9 +4521,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-04-23 23:46:46.768925, \n", + " \tcreated_at = 2024-05-13 11:52:41.967220, \n", " \n", - " \tupdated_at = 2024-04-23 23:46:46.768925, \n", + " \tupdated_at = 2024-05-13 11:52:41.967220, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2211,7 +4541,7 @@ " ), fuzzy=296.55)]" ] }, - "execution_count": 48, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -2231,7 +4561,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -2259,52 +4589,24 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[App(filename='csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='csv_bulgetest'),\n", - " App(filename='csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='csv_tensile_test'),\n", - " App(filename='csv_tensile_test_f2-Copy1/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='csv_tensile_test_f2-Copy1'),\n", - " App(filename='csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='csv_tensile_test_f2'),\n", - " App(filename='csv_upload_test/data2rdf/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='csv_upload_test/data2rdf'),\n", - " App(filename='excel_component_test/excel_component_test.ipynb', basename='excel_component_test.ipynb', folder='excel_component_test'),\n", - " App(filename='excel_nakajima_test/excel_nakajima_test.ipynb', basename='excel_nakajima_test.ipynb', folder='excel_nakajima_test'),\n", - " App(filename='excel_notch_test/excel_notch_tensile_test.ipynb', basename='excel_notch_tensile_test.ipynb', folder='excel_notch_test'),\n", - " App(filename='excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='excel_tensile_test'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/material_card_export.ipynb', basename='material_card_export.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification_voila_version.ipynb', basename='tensiletest_parameter_identification_voila_version.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/browse_linked_kitems.ipynb', basename='browse_linked_kitems.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='stahldigital_workshop_2024_base_folder/Teilnehmer/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/data2rdf/csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='stahldigital_workshop_2024_base_folder/data2rdf/csv_tensile_test'),\n", - " App(filename='stahldigital_workshop_2024_base_folder/data2rdf/excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='stahldigital_workshop_2024_base_folder/data2rdf/excel_tensile_test'),\n", - " App(filename='tensiletest-parameter-identification/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='tensiletest-parameter-identification'),\n", - " App(filename='tensiletest-parameter-identification/tensiletest_parameter_identification_voila.ipynb', basename='tensiletest_parameter_identification_voila.ipynb', folder='tensiletest-parameter-identification'),\n", - " App(filename='tensiletest-parameter-identification/voila_test.ipynb', basename='voila_test.ipynb', folder='tensiletest-parameter-identification'),\n", - " App(filename='test/Modul_Datennutzung/Auswertung_Zugversuch/material_card_export.ipynb', basename='material_card_export.ipynb', folder='test/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='test/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='test/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='test/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='test/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test1/Modul_Datennutzung/Auswertung_Zugversuch/material_card_export.ipynb', basename='material_card_export.ipynb', folder='test1/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='test1/Modul_Datennutzung/Auswertung_Zugversuch/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='test1/Modul_Datennutzung/Auswertung_Zugversuch'),\n", - " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/access_kitem.ipynb', basename='access_kitem.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/generate_Kitem_and_upload.ipynb', basename='generate_Kitem_and_upload.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/search_and_plot.ipynb', basename='search_and_plot.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/search_and_show_meta_data.ipynb', basename='search_and_show_meta_data.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps'),\n", - " App(filename='test1/Modul_Datennutzung/Beispiel_Apps/unit_conversion.ipynb', basename='unit_conversion.ipynb', folder='test1/Modul_Datennutzung/Beispiel_Apps')]" + "[App(filename='dsms-app-initiator/csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='dsms-app-initiator/csv_bulgetest'),\n", + " App(filename='dsms-app-initiator/csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='dsms-app-initiator/csv_tensile_test'),\n", + " App(filename='dsms-app-initiator/csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='dsms-app-initiator/csv_tensile_test_f2'),\n", + " App(filename='dsms-app-initiator/excel_component_test/excel_component_test.ipynb', basename='excel_component_test.ipynb', folder='dsms-app-initiator/excel_component_test'),\n", + " App(filename='dsms-app-initiator/excel_nakajima_test/excel_nakajima_test.ipynb', basename='excel_nakajima_test.ipynb', folder='dsms-app-initiator/excel_nakajima_test'),\n", + " App(filename='dsms-app-initiator/excel_notch_test/excel_notch_tensile_test.ipynb', basename='excel_notch_tensile_test.ipynb', folder='dsms-app-initiator/excel_notch_test'),\n", + " App(filename='dsms-app-initiator/excel_shear_test/excel_shear_test.ipynb', basename='excel_shear_test.ipynb', folder='dsms-app-initiator/excel_shear_test'),\n", + " App(filename='dsms-app-initiator/excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='dsms-app-initiator/excel_tensile_test'),\n", + " App(filename='graph_viz/graph_viz.ipynb', basename='graph_viz.ipynb', folder='graph_viz')]" ] }, - "execution_count": 50, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -2329,7 +4631,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -2383,7 +4685,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -2392,7 +4694,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ From 9a8e4cf47f728070d0cdd16f5239e593b48edc7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 13 May 2024 14:50:21 +0200 Subject: [PATCH 066/114] Bump version v1.4.0rc12 -> v1.4.0rc13 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4559d2e..bc3ac6c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc12 +version = v1.4.0rc13 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From c38b95dfea90289a4266545b932fd6bd96ecede9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 13 May 2024 17:39:28 +0200 Subject: [PATCH 067/114] fix linked kitems validator --- dsms/knowledge/properties/linked_kitems.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index a98da02..e8a373b 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -188,10 +188,12 @@ def serialize_author(self) -> Dict[str, Any]: @field_validator("custom_properties") @classmethod def validate_custom_properties( - cls, value: Dict[str, Any] - ) -> "Dict[str, Any]": + cls, value: "Optional[Dict[str, Any]]" + ) -> "Optional[Dict[str, Any]]": """Validate the custom properties of the linked KItem""" - return value.get("content") or value + if value: + value = value.get("content") or value + return value class LinkedKItemsProperty(KItemPropertyList): From 1dedbd4feccb09ac257b88d491c3042a9dbb24d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 13 May 2024 17:45:58 +0200 Subject: [PATCH 068/114] Bump version v1.4.0rc13 -> v1.4.0rc14 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bc3ac6c..1c635b4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc13 +version = v1.4.0rc14 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From d54364005d6186c1eb9590fe14ab310929d3f4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 14 May 2024 00:02:40 +0200 Subject: [PATCH 069/114] debug numerical data type for displaying units, debug utilities for committing --- dsms/knowledge/properties/affiliations.py | 2 +- dsms/knowledge/properties/annotations.py | 7 +- dsms/knowledge/properties/apps.py | 2 +- dsms/knowledge/properties/attachments.py | 2 +- dsms/knowledge/properties/authors.py | 2 +- dsms/knowledge/properties/contacts.py | 2 +- dsms/knowledge/properties/external_links.py | 2 +- dsms/knowledge/properties/hdf5.py | 4 +- dsms/knowledge/properties/linked_kitems.py | 2 +- dsms/knowledge/properties/summary.py | 2 +- dsms/knowledge/properties/user_groups.py | 2 +- dsms/knowledge/utils.py | 12 ++- examples/unit_conversion.ipynb | 2 +- tests/test_utils.py | 87 +++++++++++++++------ 14 files changed, 87 insertions(+), 43 deletions(-) diff --git a/dsms/knowledge/properties/affiliations.py b/dsms/knowledge/properties/affiliations.py index 79a67df..bb0297c 100644 --- a/dsms/knowledge/properties/affiliations.py +++ b/dsms/knowledge/properties/affiliations.py @@ -1,4 +1,4 @@ -"""Affiliation KItemPropertyList""" +"""Affiliation property of a KItem""" from typing import TYPE_CHECKING diff --git a/dsms/knowledge/properties/annotations.py b/dsms/knowledge/properties/annotations.py index a6aab5a..860c464 100644 --- a/dsms/knowledge/properties/annotations.py +++ b/dsms/knowledge/properties/annotations.py @@ -1,6 +1,6 @@ -"""Annotations KItemPropertyList""" +"""Annotation property of a KItem""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from pydantic import Field @@ -17,6 +17,9 @@ class Annotation(KItemProperty): iri: str = Field(..., description="IRI of the annotation") name: str = Field(..., description="Name of the annotation") namespace: str = Field(..., description="Namespace of the annotation") + description: Optional[str] = Field( + None, description="Description of the annotation" + ) class AnnotationsProperty(KItemPropertyList): diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index d89c08d..a0496ce 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -1,4 +1,4 @@ -"""App KItemPropertyList""" +"""App property of a KItem""" from typing import TYPE_CHECKING, Any, Dict, List, Optional diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 0bd2094..54c2383 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -1,4 +1,4 @@ -"""Attachment KItemPropertyList""" +"""Attachment property of a KItem""" from pathlib import Path from typing import TYPE_CHECKING diff --git a/dsms/knowledge/properties/authors.py b/dsms/knowledge/properties/authors.py index 2fedecd..66738f3 100644 --- a/dsms/knowledge/properties/authors.py +++ b/dsms/knowledge/properties/authors.py @@ -1,4 +1,4 @@ -"""Author KItemPropertyList""" +"""Author property of a KItem""" from typing import TYPE_CHECKING, Any, Dict from uuid import UUID diff --git a/dsms/knowledge/properties/contacts.py b/dsms/knowledge/properties/contacts.py index 7070154..70353cb 100644 --- a/dsms/knowledge/properties/contacts.py +++ b/dsms/knowledge/properties/contacts.py @@ -1,4 +1,4 @@ -"""DContacts KItemPropertyList""" +"""Contacts property of a KItem""" from typing import TYPE_CHECKING, Optional diff --git a/dsms/knowledge/properties/external_links.py b/dsms/knowledge/properties/external_links.py index 177613f..937d6a7 100644 --- a/dsms/knowledge/properties/external_links.py +++ b/dsms/knowledge/properties/external_links.py @@ -1,4 +1,4 @@ -"""ExternalLink KItemPropertyList""" +"""ExternalLink property of a KItem""" from typing import TYPE_CHECKING diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/hdf5.py index dc9c279..b350d59 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/hdf5.py @@ -1,4 +1,4 @@ -"""HDF5 Properties of a KItem""" +"""HDF5 property of a KItem""" import logging from typing import TYPE_CHECKING @@ -36,7 +36,7 @@ class Column(KItemProperty): def __repr__(self) -> str: """Pretty print the numerical datatype""" - if self.kitem.dsms.config.display_units: + if self.kitem and self.kitem.dsms.config.display_units: try: unit = f"\tunit={self.get_unit().get('symbol')}\n\t\t" string = str(self) diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index e8a373b..f2f8f21 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -1,4 +1,4 @@ -"""Linked KItems KItemPropertyList""" +"""Linked KItems of a KItem""" from datetime import datetime from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union diff --git a/dsms/knowledge/properties/summary.py b/dsms/knowledge/properties/summary.py index c8eed0c..ae694a0 100644 --- a/dsms/knowledge/properties/summary.py +++ b/dsms/knowledge/properties/summary.py @@ -1,4 +1,4 @@ -"""Custom properties of a KItem""" +"""Summary of a KItem""" from typing import TYPE_CHECKING, Any, Optional diff --git a/dsms/knowledge/properties/user_groups.py b/dsms/knowledge/properties/user_groups.py index ade56ba..4c5fe7a 100644 --- a/dsms/knowledge/properties/user_groups.py +++ b/dsms/knowledge/properties/user_groups.py @@ -1,4 +1,4 @@ -"""UserGroup KItemPropertyList""" +"""UserGroup property of a KItem""" from typing import TYPE_CHECKING diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index ec8e745..47dbeb7 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -375,13 +375,17 @@ def _get_apps_diff( ) -> "Dict[str, List[Dict[str, Any]]]": """Get differences in kitem apps from previous KItem state""" differences = {} - old_linked = old_kitem.get("kitem_apps") - new_linked = [new.model_dump() for new in new_kitem.kitem_apps] + exclude = {"id", "kitem_app_id"} + old_apps = [ + {key: value for key, value in old.items() if key not in exclude} + for old in old_kitem.get("kitem_apps") + ] + new_apps = [new.model_dump() for new in new_kitem.kitem_apps] differences["kitem_apps_to_update"] = [ - attr for attr in new_linked if attr not in old_linked + attr for attr in new_apps if attr not in old_apps ] differences["kitem_apps_to_remove"] = [ - attr for attr in old_linked if attr not in new_linked + attr for attr in old_apps if attr not in new_apps ] logger.debug("Found differences in KItem apps: %s", differences) return differences diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 5c8cbdc..0ff0128 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -1257,7 +1257,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.12.2" } }, "nbformat": 4, diff --git a/tests/test_utils.py b/tests/test_utils.py index aaadd0d..2ec04a8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -51,35 +51,58 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): name="bar123", ) - annotation = { - "iri": "http://example.org/", - "name": "foo", - "namespace": "example", - } - annotation2 = { - "iri": "http://example.org/", - "name": "bar", - "namespace": "example", - } user_group = {"name": "private", "group_id": "private_123"} app = {"executable": "foo.exe"} - app2 = {"executable": "bar.exe"} - kitem_old = KItem( - id=get_mock_kitem_ids[0], - name="foo123", - ktype_id=dsms.ktypes.Organization, - annotations=[annotation2], - linked_kitems=[linked_kitem1, linked_kitem2], - user_groups=[user_group], - kitem_apps=[app2], - ) + kitem_old = { + "id": get_mock_kitem_ids[0], + "name": "foo123", + "ktype_id": dsms.ktypes.Organization.value, + "annotations": [ + { + "iri": "http://example.org/", + "name": "bar", + "namespace": "example", + "description": None, + } + ], + "linked_kitems": [ + { + "id": str(get_mock_kitem_ids[1]), + "ktype_id": dsms.ktypes.Organization.value, + "name": "foo456", + }, + { + "id": str(get_mock_kitem_ids[2]), + "ktype_id": dsms.ktypes.Organization.value, + "name": "foo789", + }, + ], + "user_groups": [user_group], + "kitem_apps": [ + { + "id": get_mock_kitem_ids[0], + "kitem_app_id": 17, + "executable": "bar.exe", + "title": None, + "description": None, + "tags": None, + "additional_properties": None, + } + ], + } kitem_new = KItem( id=get_mock_kitem_ids[0], name="foo123", ktype_id=dsms.ktypes.Organization, - annotations=[annotation], + annotations=[ + { + "iri": "http://example.org/", + "name": "foo", + "namespace": "example", + } + ], linked_kitems=[linked_kitem3], user_groups=[user_group], kitem_apps=[app], @@ -89,7 +112,14 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): "kitems_to_link": [ {"id": str(obj.id)} for obj in kitem_new.linked_kitems ], - "annotations_to_link": [annotation], + "annotations_to_link": [ + { + "iri": "http://example.org/", + "name": "foo", + "namespace": "example", + "description": None, + } + ], "user_groups_to_add": [], "kitem_apps_to_update": [ { @@ -101,9 +131,16 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): } ], "kitems_to_unlink": [ - {"id": str(obj.id)} for obj in kitem_old.linked_kitems + {"id": str(linked.id)} for linked in [linked_kitem1, linked_kitem2] + ], + "annotations_to_unlink": [ + { + "iri": "http://example.org/", + "name": "bar", + "namespace": "example", + "description": None, + } ], - "annotations_to_unlink": [annotation2], "user_groups_to_remove": [], "kitem_apps_to_remove": [ { @@ -115,7 +152,7 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): } ], } - diffs = _get_kitems_diffs(kitem_old.model_dump(), kitem_new) + diffs = _get_kitems_diffs(kitem_old, kitem_new) for key, value in diffs.items(): assert value == expected.pop(key) From d2bc9d47a12d943bc0f4fb8c3dd09c95d82a9b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 14 May 2024 00:10:37 +0200 Subject: [PATCH 070/114] Bump version v1.4.0rc14 -> v1.4.0rc15 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1c635b4..58cc984 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc14 +version = v1.4.0rc15 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From d41f384a5694f5cfef4e23a20f5823515ea45a1a Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Wed, 15 May 2024 09:37:09 +0000 Subject: [PATCH 071/114] Enabling Docker, dedicated README.md for docs, fixing installtion.md --- Dockerfile.docs | 34 +++++++ docs/README.md | 67 ++++++++++++++ docs/source/README.md | 38 ++++++++ .../assets/Dummy_Tensile_Test_Data.xlsx | Bin 6724 -> 0 bytes docs/source/installation.md | 47 ++++++---- docs/source/tutorials/2_creation.ipynb | 13 ++- docs/source/tutorials/3_updation.ipynb | 26 ++++-- docs/source/tutorials/4_deletion.ipynb | 10 +++ docs/source/tutorials/5_search.ipynb | 83 ++++++++++++------ 9 files changed, 266 insertions(+), 52 deletions(-) create mode 100644 Dockerfile.docs create mode 100644 docs/README.md create mode 100644 docs/source/README.md delete mode 100644 docs/source/assets/Dummy_Tensile_Test_Data.xlsx diff --git a/Dockerfile.docs b/Dockerfile.docs new file mode 100644 index 0000000..cfb8d91 --- /dev/null +++ b/Dockerfile.docs @@ -0,0 +1,34 @@ +# Use a base image with the latest available Python version (adjust as necessary) +FROM python:3.9-buster + +# Ensure the package list is up-to-date and upgrade existing packages +RUN apt-get update && apt-get upgrade -y + +# Install required packages, ensuring the latest versions are installed +RUN apt-get install -y --no-install-recommends pandoc default-jre graphviz +RUN apt-get install -y --no-install-recommends texlive-latex-recommended \ + texlive-latex-extra \ + texlive-fonts-recommended \ + latexmk + +# Clean up the apt cache to reduce image size +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy the requirements file for documentation +COPY docs/requirements.txt /app/docs/requirements.txt + +# Install Python packages for documentation from requirements.txt, ensuring the latest versions are installed +RUN pip install --upgrade pip +RUN pip install --upgrade -r /app/docs/requirements.txt + +# Copy the entire repository to the container +COPY . /app + +# Install the SDK in editable mode +RUN pip install -e /app + +# Define the command to run the application +CMD ["sphinx-autobuild", "--host", "0.0.0.0", "docs/source", "docs/build/html"] diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b426787 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,67 @@ +# DSMS-SDK Docs +To access the documentation, please visit: + + +If you find any error or problem with the documentation, please create an issue [in this repository](https://github.com/MI-FraunhoferIWM/dsms-python-sdk/issues). + +## Local Rendering + + +### HTML +A server will start, generate the docs and listen for changes in the source files. +This can be done by using docker or installing the development environment directly on the you machine. Next are installation guides for Docker and Linux OS. + +#### Docker +First, build the Docker image by running the following command: +```shell +$ docker build -f Dockerfile.docs -t sphinx-docs . +``` + +Then, start the program by running: +```shell +$ docker run --rm -v $PWD:/app -p 8000:8000 sphinx-docs +``` + +#### Linux +At an OS level (these commands work on Linux Debian): +```shell +$ sudo apt install pandoc graphviz default-jre +$ sudo apt-get install texlive-latex-recommended \ + texlive-latex-extra \ + texlive-fonts-recommended \ + latexmk +``` +The python dependencies: +```shell +$ pip install -r requirements.txt +``` + +Now you can start the server and render the docs: +``` +$ sphinx-autobuild docs/source docs/build/html +``` +The documentation will be available on [`http://127.0.0.1:8000`](http://127.0.0.1:8000). + +#### VSCode + +To render the documentation using VSCode, follow these steps: + +1. **Ensure Live Server Extension is Installed:** + + You need to have the Live Server extension installed in VSCode. If you don't have it installed, you can find it [here](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer). + +2. **Build the Documentation:** + + Open your terminal in VSCode and run the following command to build the HTML documentation: + +```shell +$ make html +``` +3. **Open the Documentation with Live Server:** + + After building the documentation, navigate to the docs/build/html directory in VSCode. + Right-click on the index.html file and select the option "Open with Live Server". + +4. **Access the Documentation:** + + The documentation will open in your default web browser and be accessible at http://127.0.0.1:5500. diff --git a/docs/source/README.md b/docs/source/README.md new file mode 100644 index 0000000..7703aba --- /dev/null +++ b/docs/source/README.md @@ -0,0 +1,38 @@ +# DSMS-SDK +Python SDK core-package for interacting with the Dataspace Management System (DSMS) + + +## Authors + +[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) + +[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) + +[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) + +## License + +This project is licensed under the BSD 3-Clause. See the LICENSE file for more information. + +## Usage + +The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities: + +- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType) + - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test + - Administrating authorship, contact information and supplementary information upon making changes or adding KItems + - Semantic annotation of KItems +- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface +- Linking KItems to other KItems +- Linking Apps to KItems, triggererd, for example, during a file upload +- Performing simple file upload and download using attachments to KItems +- Export of a knowledge (sub) graph as common serializations (.ttl, .json) + +For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items. + + +## Disclaimer + +Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM. + +Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) diff --git a/docs/source/assets/Dummy_Tensile_Test_Data.xlsx b/docs/source/assets/Dummy_Tensile_Test_Data.xlsx deleted file mode 100644 index d5bf8d41783b6f85328a04f1acbe7267bf3261c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6724 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 diff --git a/docs/source/installation.md b/docs/source/installation.md index ad707f9..5aaf60b 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -9,13 +9,6 @@ Before using DSMS-SDK make sure to register your account at your DSMS Instance.Y - [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) -The following are the instances of the DSMS you could choose from: - -- [StahlDigital](https://lnkd.in/gfwe9a36) -- [KupferDigital](https://lnkd.in/g8mvnM3K) -- [DiMAT](https://lnkd.in/g46baB6J) - - After following the above steps, you could use either of the two ways to install DSMS to your machine. #### Method 1 : Via PyPI #### @@ -37,28 +30,52 @@ Install in your folder (or any folder you like). You need to authenticate yourself to connect with dsms-core using dsms-python-sdk. Therefore follow the following process: -1. Pick the DSMS host of your choice and -2. How to get the token -Step 1: Login into the selected data.space instance of your choice. +1. **Pick the DSMS host of your choice.** + + The following are the instances of the DSMS you could choose from: + +- [StahlDigital](https://lnkd.in/gfwe9a36) +- [KupferDigital](https://lnkd.in/g8mvnM3K) +- [DiMAT](https://lnkd.in/g46baB6J) + +2. **Add the relevant information in the .env file for setting up the login with DSMS core via SDK.** + + Step 1 : Create a .env file in the following folders + + a. {path_to_dsms-python-sdk}/dsms-python-sdk/.env + + b. {path_to_dsms-python-sdk}/dsms-python-sdk/docs/source/.env + + Step 2 : Populate both the .env file with the following information. Here, we choose the stahldigital instance for demo : + + DSMS_HOST_URL = https://stahldigital.materials-data.space + DSMS_USERNAME = {YOUR_USERNAME} + DSMS_PASSWORD = {YOUR_PASSWORD} + + Now Save the file and the user is ready to connect with DSMS-Core via SDK. + +**3. How to get the token [Optional]** + + Step 1: Login into the selected data.space instance of your choice. ![copy_token_1](assets/images/copy_token_1.jpg) -Step 2: Enter your credentials + Step 2: Enter your credentials ![copy_token_2](assets/images/copy_token_2.jpg) -Step 3: After logging in you will land up in the home page. Now look at the my profile option + Step 3: After logging in you will land up in the home page. Now look at the my profile option ![copy_token_3](assets/images/copy_token_3.jpg) -Step 4: The my profile option should look something like below. Now click on the Advanced. + Step 4: The my profile option should look something like below. Now click on the Advanced. ![copy_token_4](assets/images/copy_token_4.jpg) -Step 5: The Advanced section should like the below. Now click on the copy token to clipboard + Step 5: The Advanced section should like the below. Now click on the copy token to clipboard ![copy_token_6](assets/images/copy_token_5.jpg) -Step 6: Now paste it in DSMS_TOKEN attribute of the .env file in root/dsms-python-sdk. + Step 6: Now paste it in DSMS_TOKEN attribute of the .env file in root/dsms-python-sdk. Now you are ready to use dsms-sdk. Do check out the tutorials section to try out on some basic examples on how to use dsms-sdk. diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index bc41e2c..49d2d62 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -82,9 +82,12 @@ "outputs": [], "source": [ "item = KItem(\n", - " name=\"foo123\",\n", - " ktype_id=dsms.ktypes.Dataset,\n", - " custom_properties={\"foo\": \"bar\"},\n", + " name=\"Machine-1\",\n", + " ktype_id=dsms.ktypes.TestingMachine,\n", + " custom_properties={\"Producer\": \"TestingLab GmBH\",\n", + " \"Room Number\": \"A404\",\n", + " \"Description\" : \"Bending Test Machine\"\n", + " },\n", ")\n", "\n", "item" @@ -120,7 +123,9 @@ "metadata": {}, "outputs": [], "source": [ - "item" + "item\n", + "item.name\n", + "type(item)" ] }, { diff --git a/docs/source/tutorials/3_updation.ipynb b/docs/source/tutorials/3_updation.ipynb index e128051..f447cfa 100644 --- a/docs/source/tutorials/3_updation.ipynb +++ b/docs/source/tutorials/3_updation.ipynb @@ -60,6 +60,16 @@ "dsms.kitems" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item = dsms.kitems[0]\n", + "print(item.name)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -85,16 +95,16 @@ "metadata": {}, "outputs": [], "source": [ - "item.name = \"foobar\"\n", - "item.custom_properties.foobar = \"foobar\"\n", + "item.name = \"Machine-1\"\n", + "item.custom_properties.Producer = \"Machinery GmBH\"\n", "item.attachments.append(\"../README.md\")\n", - "item.annotations.append(\"www.example.org/foo\")\n", + "item.annotations.append(\"www.machinery.org/\")\n", "item.external_links.append(\n", - " {\"url\": \"http://example.org\", \"label\": \"example link\"}\n", + " {\"url\": \"http://machine.org\", \"label\": \"machine-link\"}\n", ")\n", - "item.contacts.append({\"name\": \"foo\", \"email\": \"foo@bar.mail\"})\n", - "item.affiliations.append(\"foobar team\")\n", - "item.user_groups.append({\"name\": \"foogroup\", \"group_id\": \"123\"})" + "item.contacts.append({\"name\": \"machinesupport\", \"email\": \"machinesupport@group.mail\"})\n", + "item.affiliations.append(\"machine-team\")\n", + "item.user_groups.append({\"name\": \"machinegroup\", \"group_id\": \"123\"})" ] }, { @@ -152,7 +162,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "0.0.0" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/docs/source/tutorials/4_deletion.ipynb b/docs/source/tutorials/4_deletion.ipynb index 283409a..5e7cbcb 100644 --- a/docs/source/tutorials/4_deletion.ipynb +++ b/docs/source/tutorials/4_deletion.ipynb @@ -61,6 +61,16 @@ "dsms.kitems" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "item = dsms.kitems[0]\n", + "print(item)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/docs/source/tutorials/5_search.ipynb b/docs/source/tutorials/5_search.ipynb index 3349be2..f65c895 100644 --- a/docs/source/tutorials/5_search.ipynb +++ b/docs/source/tutorials/5_search.ipynb @@ -87,35 +87,53 @@ "metadata": {}, "outputs": [], "source": [ - "item = KItem(\n", - " name=\"foo 1\",\n", - " ktype_id=dsms.ktypes.DatasetCatalog\n", + "item1 = KItem(\n", + " name=\"Machine-1\",\n", + " ktype_id=dsms.ktypes.TestingMachine,\n", + " custom_properties={\"Producer\": \"TestingLab GmBH\",\n", + " \"Room Number\": \"A404\",\n", + " \"Description\": \"Bending Test Machine\"\n", + " }\n", ")\n", "\n", "item2 = KItem(\n", - " name=\"foo 2\",\n", - " ktype_id=dsms.ktypes.Organization,\n", - " linked_kitems=[item],\n", - " annotations=[\n", - " {\n", - " \"iri\": \"www.example.org/foo\",\n", - " \"name\": \"foo\",\n", - " \"namespace\": \"www.example.org\",\n", - " }\n", - " ],\n", + " name=\"Machine-2\",\n", + " ktype_id=dsms.ktypes.TestingMachine,\n", + " custom_properties={\"Producer\": \"StressStrain GmBH\",\n", + " \"Room Number\": \"B500\",\n", + " \"Description\": \"Compression Test Machine\"\n", + " }\n", ")\n", + "\n", "item3 = KItem(\n", - " name=\"foo 3\", \n", - " ktype_id=dsms.ktypes.Organization\n", + " name=\"Specimen-1\", \n", + " ktype_id=dsms.ktypes.Specimen,\n", + " linked_kitems=[item1],\n", + " custom_properties={\"Geometry\": \"Cylindrical 150mm x 20mm x 40mm\",\n", + " \"Material\": \"Concrete\",\n", + " \"Project ID\": \"ConstructionProject2024\"\n", + " }\n", + "\n", ")\n", "item4 = KItem(\n", - " name=\"foo 4\",\n", + " name=\"Specimen-2\",\n", + " ktype_id=dsms.ktypes.Specimen,\n", + " linked_kitems=[item2],\n", + " custom_properties={\"Geometry\": \"Rectangular 200mm x 30mm x 20mm\",\n", + " \"Material\": \"Metal\",\n", + " \"Project ID\": \"MetalBlenders2024\"\n", + " }\n", + ")\n", + "\n", + "item5 = KItem(\n", + " name=\"Fraunhofer BAC\",\n", " ktype_id=dsms.ktypes.Organization,\n", + " linked_kitems=[item1],\n", " annotations=[\n", " {\n", - " \"iri\": \"www.example.org/bar\",\n", - " \"name\": \"bar\",\n", - " \"namespace\": \"https://www.example.org\",\n", + " \"iri\": \"www.fraunhoferBACiri.org/foo\",\n", + " \"name\": \"fraunhoferBACInstitute\",\n", + " \"namespace\": \"www.fraunhoferBAC.org\",\n", " }\n", " ],\n", ")\n", @@ -136,7 +154,16 @@ "metadata": {}, "outputs": [], "source": [ - "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" + "dsms.search(ktypes=[dsms.ktypes.TestingMachine])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.kitems" ] }, { @@ -159,7 +186,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "... or for all of type `DatasetCatalog` with `foo` in the name:" + "... or for all of type `Dataset` with `Specimen-1` in the name:" ] }, { @@ -168,7 +195,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" + "dsms.search(query=\"Specimen-1\", ktypes=[dsms.ktypes.Dataset])" ] }, { @@ -185,7 +212,7 @@ "outputs": [], "source": [ "dsms.search(\n", - " ktypes=[dsms.ktypes.Organization], annotations=[\"www.example.org/foo\"]\n", + " ktypes=[dsms.ktypes.Organization], annotations=[\"www.fraunhoferBACiri.org/foo\"]\n", " )" ] }, @@ -202,13 +229,19 @@ "metadata": {}, "outputs": [], "source": [ - "del dsms[item]\n", + "del dsms[item1]\n", "del dsms[item2]\n", "del dsms[item3]\n", "del dsms[item4]\n", + "del dsms[item5]\n", "\n", "dsms.commit()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { @@ -227,7 +260,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "0.0.0" + "version": "3.10.13" } }, "nbformat": 4, From efe3bb777b775b601e742ac877cadcbab30ac905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 15 May 2024 22:07:45 +0200 Subject: [PATCH 072/114] add functionality to make avatar --- dsms/knowledge/kitem.py | 17 +++++- dsms/knowledge/properties/__init__.py | 3 + dsms/knowledge/properties/avatar.py | 33 +++++++++++ dsms/knowledge/utils.py | 83 +++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 dsms/knowledge/properties/avatar.py diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 2727568..cde5f96 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -29,6 +29,7 @@ AnnotationsProperty, App, AppsProperty, + Avatar, Attachment, AttachmentsProperty, Author, @@ -185,11 +186,15 @@ class KItem(BaseModel): False, description="Whether the KItem holds an RDF Graph or not." ) + avatar: Optional[Union[Avatar, Dict]] = Field( + default_factory=Avatar, description="KItem avatar interface" + ) + model_config = ConfigDict( extra="forbid", validate_assignment=True, validate_default=True, - exclude={"ktype"}, + exclude={"ktype", "avatar"}, arbitrary_types_allowed=True, ) @@ -491,6 +496,14 @@ def validate_summary(cls, value: Union[str, Summary]) -> Summary: value = Summary(kitem=cls, text=value) return value + @field_validator("avatar", mode="before") + @classmethod + def validate_avatar(cls, value: "Union[Dict, Avatar]") -> Avatar: + """Validate avatar""" + if isinstance(value, dict): + value = Avatar(kitem=cls, **value) + return value + @field_validator("hdf5") @classmethod def validate_hdf5( @@ -559,7 +572,7 @@ def _set_kitem_for_properties(self) -> None: """ for prop in self.__dict__.values(): if ( - isinstance(prop, (KItemPropertyList, Summary)) + isinstance(prop, (KItemPropertyList, Summary, Avatar)) and not prop.kitem ): logger.debug( diff --git a/dsms/knowledge/properties/__init__.py b/dsms/knowledge/properties/__init__.py index d3594df..3d5c9b5 100644 --- a/dsms/knowledge/properties/__init__.py +++ b/dsms/knowledge/properties/__init__.py @@ -33,6 +33,8 @@ ExternalLinksProperty, ) +from dsms.knowledge.properties.avatar import Avatar # isort:skip + __all__ = [ "Annotation", "AnnotationsProperty", @@ -40,6 +42,7 @@ "AttachmentsProperty", "Author", "AuthorsProperty", + "Avatar", "LinkedKItem", "LinkedKItemsProperty", "ContactInfo", diff --git a/dsms/knowledge/properties/avatar.py b/dsms/knowledge/properties/avatar.py new file mode 100644 index 0000000..f2fe1e9 --- /dev/null +++ b/dsms/knowledge/properties/avatar.py @@ -0,0 +1,33 @@ +"""DSMS KItem Avatar""" + +from typing import Optional, Union + +from PIL.Image import Image +from pydantic import ConfigDict, Field + +from dsms.knowledge.properties.base import KItemProperty +from dsms.knowledge.utils import _get_avatar, _make_avatar + + +class Avatar(KItemProperty): + """DSMS KItem Avatar""" + + file: Optional[Union[Image, str]] = Field( + None, + description="The file path to the image when setting a new avatar is set", + ) + include_qr: Optional[bool] = Field( + False, + description="""Whether the new avatar is supposed to include a qr. + This can be combined with an image file.""", + ) + + model_config = ConfigDict(arbitrary_types_allowed=True) + + def download(self) -> "Image": + """Download avatar as PIL Image""" + return _get_avatar(self.kitem) + + def generate(self) -> "Image": + """Generate avatar as PIL Image""" + return _make_avatar(self.kitem, self.file, self.include_qr) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 47dbeb7..d059a22 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -1,4 +1,5 @@ """DSMS knowledge utilities""" +import base64 import io import logging import re @@ -9,6 +10,8 @@ from uuid import UUID import pandas as pd +import segno +from PIL import Image from requests import Response from pydantic import ( # isort: skip @@ -262,6 +265,7 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: payload = new_kitem.model_dump( exclude={ "authors", + "avatar", "annotations", "custom_properties", "linked_kitems", @@ -288,6 +292,14 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: ) if new_kitem.custom_properties: custom_properties = new_kitem.custom_properties.model_dump() + # # a smarted detection whether the custom properties were updated is needed + # old_properties = old_kitem.get("custom_properties") + # if isinstance(old_properties, dict): + # old_custom_properties = old_properties.get("content") + # else: + # old_custom_properties = None + # if custom_properties != old_custom_properties: + # payload.update(custom_properties={"content": custom_properties}) payload.update(custom_properties={"content": custom_properties}) logger.debug( "Update KItem for `%s` with payload: %s", new_kitem.id, payload @@ -497,6 +509,8 @@ def _commit_updated(buffer: "Dict[str, KItem]") -> None: _delete_hdf5(new_kitem.id) _update_kitem(new_kitem, old_kitem) _update_attachments(new_kitem, old_kitem) + if new_kitem.avatar.file or new_kitem.avatar.include_qr: + _commit_avatar(new_kitem) new_kitem.in_backend = True logger.debug( "Fetching updated KItem from remote backend: %s", new_kitem.id @@ -632,3 +646,72 @@ def _update_hdf5(kitem_id: str, data: pd.DataFrame): def _delete_hdf5(kitem_id: str) -> Response: logger.debug("Delete HDF5 for kitem with id `%s`.", kitem_id) return _perform_request(f"api/knowledge/data_api/{kitem_id}", "delete") + + +def _commit_avatar(kitem) -> None: + if kitem.avatar_exists: + response = _perform_request( + f"api/knowledge/avatar/{kitem.id}", "delete" + ) + if not response.ok: + message = ( + f"Something went wrong deleting the avatar: {response.text}" + ) + raise RuntimeError(message) + avatar = kitem.avatar.generate() + buffer = io.BytesIO() + avatar.save(buffer, "JPEG", quality=100) + buffer.seek(0) + encoded_image = base64.b64encode(buffer.getvalue()) + encoded_image_str = "data:image/jpeg;base64," + encoded_image.decode( + "utf-8" + ) + response = _perform_request( + f"api/knowledge/avatar/{kitem.id}", + "put", + json={ + "croppedImage": encoded_image_str, + "originalImage": encoded_image_str, + "filename": kitem.name + ".jpeg", + }, + ) + if not response.ok: + raise RuntimeError( + f"Something went wrong while updating the avatar: {response.text}" + ) + + +def _make_avatar( + kitem: "KItem", image: Optional[Union[str, Image.Image]], make_qr: bool +) -> Image.Image: + if make_qr: + qrcode = segno.make(kitem.url) + if image: + out = io.BytesIO() + if isinstance(image, Image.Image): + raise TypeError( + """When a QR Code is generated with an image as background, + its filepath must be a string""" + ) + qrcode.to_artistic( + background=image, target=out, scale=5, kind="jpeg", border=0 + ) + avatar = Image.open(out) + else: + avatar = qrcode.to_pil(scale=5, border=0) + if image and not make_qr: + if isinstance(image, str): + avatar = Image.open(image) + else: + avatar = image + if not image and not make_qr: + raise RuntimeError( + "Cannot generate avator. Neither `include_qr` or `file` are specified." + ) + return avatar + + +def _get_avatar(kitem: "KItem") -> Image.Image: + response = _perform_request(f"api/knowledge/avatar/{kitem.id}", "get") + buffer = io.BytesIO(response.content) + return Image.open(buffer) From 93d830927e77b6260ef3c72aad1b3afbc6bade78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 15 May 2024 22:17:18 +0200 Subject: [PATCH 073/114] update setup.cfg --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 58cc984..fadffe1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,8 +26,10 @@ install_requires = pydantic>=2 pydantic-settings python-dotenv + qrcode-artistic>=3,<4 rdflib>=6,<7 requests + segno>=1.6<1.7 python_requires = >=3.8 include_package_data = True From fe7db87b834fa5dd0782e406c62c4055b2fbef41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 16 May 2024 10:03:27 +0200 Subject: [PATCH 074/114] debug setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index fadffe1..97855c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ install_requires = qrcode-artistic>=3,<4 rdflib>=6,<7 requests - segno>=1.6<1.7 + segno>=1.6,<1.7 python_requires = >=3.8 include_package_data = True From 3305ea44bea5f64cd94e1b26f27fd8eaddb3627a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 16 May 2024 10:30:00 +0200 Subject: [PATCH 075/114] fix pyling issues --- dsms/knowledge/utils.py | 1 + setup.cfg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index d059a22..aeee85e 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -684,6 +684,7 @@ def _commit_avatar(kitem) -> None: def _make_avatar( kitem: "KItem", image: Optional[Union[str, Image.Image]], make_qr: bool ) -> Image.Image: + avatar = None if make_qr: qrcode = segno.make(kitem.url) if image: diff --git a/setup.cfg b/setup.cfg index 97855c1..f6cf91a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,7 @@ dev = dunamai==1.7.0 pre_commit = pre-commit==3.3.2 - pylint + pylint==3.2.0 tests = pytest==6.2.5 pytest-mock From 19e0f896a618de3e25af4934cc6189f7c2192f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 16 May 2024 14:24:32 +0200 Subject: [PATCH 076/114] add funtion to KItem --- dsms/knowledge/kitem.py | 7 +++++++ dsms/knowledge/utils.py | 1 + tests/conftest.py | 4 ++++ tests/test_kitem.py | 10 ++++++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index cde5f96..4afd9b6 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -616,6 +616,13 @@ def url(cls) -> str: f"knowledge/{cls._get_ktype_as_str()}/{cls.slug}", ) + def is_a(self, to_be_compared: KType) -> bool: + """Check the KType of the KItem""" + return ( + self.ktype_id.value # pylint: disable=no-member + == to_be_compared.value + ) + def _get_ktype_as_str(self) -> str: if isinstance(self.ktype_id, str): ktype = self.ktype_id diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index aeee85e..3241b4a 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -686,6 +686,7 @@ def _make_avatar( ) -> Image.Image: avatar = None if make_qr: + # this should be moved to the backend sooner or later qrcode = segno.make(kitem.url) if image: out = io.BytesIO() diff --git a/tests/conftest.py b/tests/conftest.py index 00a5b24..138e64b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -94,6 +94,10 @@ def return_ktypes(request): "name": "organization", "id": "organization", }, + { + "name": "dataset", + "id": "dataset", + }, ] header = {"content_type": "application/json"} return (200, header, json.dumps(ktypes)) diff --git a/tests/test_kitem.py b/tests/test_kitem.py index 6eaf7de..b1a1cdf 100644 --- a/tests/test_kitem.py +++ b/tests/test_kitem.py @@ -23,6 +23,8 @@ def test_kitem_basic(custom_address, get_mock_kitem_ids): ktype_id=dsms.ktypes.Organization, ) + assert instance.is_a(dsms.ktypes.Organization) + assert isinstance(instance.dsms, DSMS) assert isinstance(instance.dsms.config, Configuration) assert Context.dsms == instance.dsms @@ -49,6 +51,8 @@ def test_kitem_config_class(custom_address, get_mock_kitem_ids): ktype_id=dsms.ktypes.Organization, ) + assert instance.is_a(dsms.ktypes.Organization) + assert isinstance(instance.dsms, DSMS) assert config == instance.dsms.config assert Context.dsms == instance.dsms @@ -82,9 +86,11 @@ def test_kitem_custom_config_env(custom_address, get_mock_kitem_ids): custom_instance = KItem( id=get_mock_kitem_ids[0], name="foo123", - ktype_id=dsms.ktypes.Organization, + ktype_id=dsms.ktypes.Dataset, ) + assert custom_instance.is_a(dsms.ktypes.Dataset) + assert str(custom_instance.dsms.config.host_url) == custom_address os.environ.pop("DSMS_HOST_URL") @@ -140,7 +146,7 @@ def test_kitem_default_ktypes(custom_address): with pytest.warns(UserWarning, match="No authentication details"): dsms = DSMS(host_url=custom_address) - assert len(dsms.ktypes) == 1 + assert len(dsms.ktypes) == 2 @responses.activate From b17db7a202fc33aab9b0fdd539097bfbffbb64a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 16 May 2024 14:25:22 +0200 Subject: [PATCH 077/114] Bump version v1.4.0rc15 -> v1.4.0rc16 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f6cf91a..be6d796 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc15 +version = v1.4.0rc16 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 1d237d192d06ff1cc591efad25df1de2fa906e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 6 Jun 2024 18:07:45 +0200 Subject: [PATCH 078/114] add functionality to download files as bytes --- dsms/knowledge/properties/attachments.py | 4 ++-- dsms/knowledge/utils.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index 54c2383..c1d4978 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -18,9 +18,9 @@ class Attachment(KItemProperty): name: str = Field(..., description="File name of the attachment") - def download(self) -> str: + def download(self, as_bytes: bool = False) -> "Union[str, bytes]": """Download attachment file""" - return _get_attachment(self.id, self.name) + return _get_attachment(self.id, self.name, as_bytes) class AttachmentsProperty(KItemPropertyList): diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 3241b4a..193aeaa 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -371,7 +371,9 @@ def _delete_attachments(kitem: "KItem", file_name: str) -> None: ) -def _get_attachment(kitem_id: "KItem", file_name: str) -> str: +def _get_attachment( + kitem_id: "KItem", file_name: str, as_bytes: bool +) -> Union[str, bytes]: """Download attachment from KItem""" url = f"api/knowledge/attachments/{kitem_id}/{file_name}" response = _perform_request(url, "get") @@ -379,7 +381,11 @@ def _get_attachment(kitem_id: "KItem", file_name: str) -> str: raise RuntimeError( f"Download for attachment `{file_name}` was not successful: {response.text}" ) - return response.text + if not as_bytes: + content = response.text + else: + content = response.content + return content def _get_apps_diff( From a381d19c27db3ed0bf98e6c722a1ce3e216d667b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 6 Jun 2024 18:10:27 +0200 Subject: [PATCH 079/114] Bump version v1.4.0rc16 -> v1.4.0rc17 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index be6d796..184968f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc16 +version = v1.4.0rc17 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 5acb194c997ad45608013b717773c61d498c64f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 10 Jun 2024 15:24:49 +0200 Subject: [PATCH 080/114] =?UTF-8?q?update=20summary=20model=20for=20linked?= =?UTF-8?q?=20kitems=C2=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsms/knowledge/properties/linked_kitems.py | 26 +++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index f2f8f21..29219e1 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -59,6 +59,16 @@ def __repr__(self) -> str: return str(self) +class LinkedKItemSummary(BaseModel): + """Summary of a linked KItem""" + + id: str = Field(..., description="ID of the linked KItem") + + text: str = Field( + ..., description="Text for the summary of the linked KItem" + ) + + class LinkedKItem(KItemProperty): """Data model of a linked KItem""" @@ -74,7 +84,7 @@ class LinkedKItem(KItemProperty): ktype_id: str = Field(..., description="Ktype ID of the linked KItem") - summary: Optional[str] = Field( + summary: Optional[Union[str, LinkedKItemSummary]] = Field( None, description="Summary of the linked KItem." ) @@ -133,14 +143,14 @@ class LinkedKItem(KItemProperty): # OVERRIDE def __str__(self) -> str: """Pretty print the linked KItem""" - values = ",\n\t\t\t".join( + values = "\n\t\t\t".join( [ f"{key}: {value}" for key, value in self.__dict__.items() if key not in self.exclude ] ) - return f"{{\n\t\t\t{values}\n\t\t}}" + return f"\n\t\t\t{values}\n\t\t" # OVERRIDE def __repr__(self) -> str: @@ -172,6 +182,16 @@ def validate_attachments_before( for attachment in value ] + @field_validator("summary", mode="after") + @classmethod + def validate_summary_before( + cls, value: Union[str, LinkedKItemSummary] + ) -> str: + """Validate summary Field""" + if isinstance(value, LinkedKItemSummary): + value = value.text + return value + # OVERRIDE @model_serializer def serialize_author(self) -> Dict[str, Any]: From d07f258feb89cb60e37e67e662c638ceb5552505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 10 Jun 2024 15:27:06 +0200 Subject: [PATCH 081/114] Bump version v1.4.0rc17 -> v1.4.0rc18 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 184968f..c901855 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc17 +version = v1.4.0rc18 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 2bd70415fc9457a1dbca02540d8feb06b647eee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 13 Jun 2024 16:59:04 +0200 Subject: [PATCH 082/114] add new app concept --- dsms/knowledge/properties/apps.py | 124 +++++++++++++++++++++++++++++- examples/run_kitem_app.py | 27 +++++++ 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 examples/run_kitem_app.py diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index a0496ce..157875e 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -1,10 +1,12 @@ """App property of a KItem""" -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from datetime import datetime +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from pydantic import BaseModel, Field, model_serializer from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList +from dsms.knowledge.utils import _perform_request if TYPE_CHECKING: from typing import Callable @@ -34,6 +36,27 @@ def __repr__(self) -> str: return str(self) +class JobStatus(BaseModel): + """Status of a job""" + + phase: str = Field(..., description="General job app status") + estimated_duration: Optional[Union[str, datetime]] = Field( + None, description="Estimated duration of the job" + ) + finished_at: Optional[Union[str, datetime]] = Field( + None, description="Datetime when the job was finished" + ) + started_at: Optional[Union[str, datetime]] = Field( + None, description="Datetime when when the job was started" + ) + message: Optional[str] = Field( + None, description="General message of the job" + ) + progress: Optional[str] = Field( + None, description="Relative number of jobs which were finished" + ) + + class App(KItemProperty): """App of a KItem.""" @@ -64,9 +87,92 @@ def serialize_author(self) -> Dict[str, Any]: if key not in ["id", "kitem_app_id"] } - def run(self, *args, **kwargs) -> None: - """Run application""" - raise NotImplementedError + def run(self, in_background=False, **kwargs) -> None: + """Run application. + + Args: + in_background (bool, optional): whether the job shall be run asychronously + (in the background). + Warning: this may lead to a request timeout for long running jobs! + Job details may not be associated anymore when this occurs. + **kwargs (Any, optional): Additional arguments to be passed to the workflow. + KItem ID is passed automatically + + """ + kwargs["kitem_id"] = str(self.id) + + if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member + name = self.executable.strip( # pylint: disable=no-member + ".argo.yaml" + ) + response = _perform_request( + f"api/knowledge/apps/argo/job/{name}", + "post", + json=kwargs, + params={"asynchronous": in_background}, + ) + if not response.ok: + raise RuntimeError( + f"Submission was not successful: {response.text}" + ) + submitted = response.json() + else: + raise TypeError("Type of app not supported yet.") + return Job(name=submitted.get("name"), executable=self.executable) + + @property + def inputs(self) -> Dict[str, Any]: + """Inputs defined for the app from the webform builder""" + if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member + name = self.executable.strip( # pylint: disable=no-member + ".argo.yaml" + ) + route = f"api/knowledge/apps/argo/{name}/inputs" + else: + raise TypeError("Inputs for type of app not supported yet.") + response = _perform_request(route, "get") + if not response.ok: + raise RuntimeError( + f"Could not fetch app input schema: {response.text}" + ) + return response.json() + + +class Job(BaseModel): + """Job running an app""" + + name: str = Field(..., description="Name of the job submitted") + + executable: str = Field( + ..., description="Name of the executable of the job" + ) + + @property + def status(self) -> JobStatus: + """Get the status of the currently running job""" + if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member + route = f"api/knowledge/apps/argo/job/{self.name}/status" + else: + raise TypeError("Status for type of app not supported yet.") + response = _perform_request(route, "get") + if not response.ok: + raise RuntimeError(f"Could not fetch job status: {response.text}") + return JobStatus(**response.json()) + + @property + def artifacts(self) -> Dict[str, Any]: + """Get the atrifcats of a finished job""" + + if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member + route = f"api/knowledge/apps/argo/job/{self.name}/artifacts" + else: + raise TypeError("Artifacts for type of app not supported yet.") + response = _perform_request(route, "get") + if not response.ok: + raise RuntimeError( + f"Could not fetch job artifacts: {response.text}" + ) + return response.json() class AppsProperty(KItemPropertyList): @@ -81,3 +187,13 @@ def k_property_item(cls) -> "Callable": @property def k_property_helper(cls) -> None: """Not defined for Apps""" + + @property + def by_title(cls) -> Dict[str, App]: + """Get apps by title""" + return {app.title: app for app in cls} + + @property + def by_exe(cls) -> Dict[str, App]: + """Get apps by executable""" + return {app.executable: app for app in cls} diff --git a/examples/run_kitem_app.py b/examples/run_kitem_app.py new file mode 100644 index 0000000..9d22517 --- /dev/null +++ b/examples/run_kitem_app.py @@ -0,0 +1,27 @@ +import time + +from dsms import DSMS + +dsms = DSMS(env="../.env") + +id = "b24b3720-851a-445f-8028-77f526fc90b9" + +kitem = dsms[id] + +print("doing it synchronously") +job = kitem.kitem_apps.by_title["Fetch from CKAN"].run(transform=True) +print(job.artifacts) + + +print("doing it asychronously") +job = kitem.kitem_apps.by_title["Fetch from CKAN"].run( + transform=True, in_background=True +) +while True: + time.sleep(1) + print("waiting...") + if job.status.phase == "Succeeded": + print("succeeded!") + break + print("task not ready yet...") +print(job.artifacts) From 5512c56f5e8df0cfd72d60f778c596e92ce754c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 13 Jun 2024 19:27:15 +0200 Subject: [PATCH 083/114] rename query parameter --- dsms/knowledge/properties/apps.py | 8 ++++---- examples/run_kitem_app.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 157875e..0124775 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -87,12 +87,12 @@ def serialize_author(self) -> Dict[str, Any]: if key not in ["id", "kitem_app_id"] } - def run(self, in_background=False, **kwargs) -> None: + def run(self, wait=True, **kwargs) -> None: """Run application. Args: - in_background (bool, optional): whether the job shall be run asychronously - (in the background). + wait (bool, optional): whether the job shall not be run asychronously + (not in the background), but the object should wait until the job finished. Warning: this may lead to a request timeout for long running jobs! Job details may not be associated anymore when this occurs. **kwargs (Any, optional): Additional arguments to be passed to the workflow. @@ -109,7 +109,7 @@ def run(self, in_background=False, **kwargs) -> None: f"api/knowledge/apps/argo/job/{name}", "post", json=kwargs, - params={"asynchronous": in_background}, + params={"wait": wait}, ) if not response.ok: raise RuntimeError( diff --git a/examples/run_kitem_app.py b/examples/run_kitem_app.py index 9d22517..829a9ce 100644 --- a/examples/run_kitem_app.py +++ b/examples/run_kitem_app.py @@ -15,7 +15,7 @@ print("doing it asychronously") job = kitem.kitem_apps.by_title["Fetch from CKAN"].run( - transform=True, in_background=True + transform=True, wait=False ) while True: time.sleep(1) From 8fcbc2be54bb3fa5781187d9a68890e405ae66f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 13 Jun 2024 22:59:59 +0200 Subject: [PATCH 084/114] Bump version v1.4.0rc18 -> v1.4.0rc19 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c901855..9a93351 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc18 +version = v1.4.0rc19 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 570f7ffcf4dfd10d0c1e81c7d2be444f2ab45071 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 27 Jun 2024 14:56:34 +0000 Subject: [PATCH 085/114] finalising dsms-sdk documentation --- docs/describe.md | 19 +- docs/source/_static/custom.css | 5 + .../source/assets/Dummy_Tensile_Test_Data.csv | 31 -- .../{dsms_picture_01.jpg => DSMS_Mod.jpg} | Bin .../images/{dsms-sdk.jpg => DSMS_SDK.jpg} | Bin docs/source/assets/images/DSMS_logo.jpg | Bin 91441 -> 0 bytes .../source/assets/images/UML_KItem_schema.jpg | Bin 0 -> 57788 bytes docs/source/conf.py | 6 +- docs/source/dsms.md | 31 ++ docs/source/dsms_config_schema.md | 60 +++ docs/source/dsms_kitem_schema.md | 187 ++++++++ docs/source/dsms_sdk.md | 163 +++++++ docs/source/index.md | 44 +- docs/source/installation.md | 81 ---- docs/source/introduction.md | 413 ------------------ docs/source/tutorials/1_introduction.ipynb | 19 +- docs/source/tutorials/2_creation.ipynb | 254 +++++++++-- docs/source/tutorials/3_updation.ipynb | 105 ++++- docs/source/tutorials/4_deletion.ipynb | 31 +- docs/source/tutorials/5_search.ipynb | 53 +-- docs/source/tutorials/6_app_HDF5.ipynb | 22 +- environment_hiwi.txt | 173 -------- examples/Chapters/1_introduction.ipynb | 139 ------ examples/Chapters/2_creation.ipynb | 168 ------- examples/Chapters/3_updation.ipynb | 171 -------- examples/Chapters/4_deletion.ipynb | 219 ---------- examples/Chapters/5_search.ipynb | 248 ----------- examples/Chapters/6_app_HDF5.ipynb | 182 -------- examples/unit_conversion.ipynb | 2 +- 29 files changed, 816 insertions(+), 2010 deletions(-) create mode 100644 docs/source/_static/custom.css delete mode 100644 docs/source/assets/Dummy_Tensile_Test_Data.csv rename docs/source/assets/images/{dsms_picture_01.jpg => DSMS_Mod.jpg} (100%) rename docs/source/assets/images/{dsms-sdk.jpg => DSMS_SDK.jpg} (100%) delete mode 100644 docs/source/assets/images/DSMS_logo.jpg create mode 100644 docs/source/assets/images/UML_KItem_schema.jpg create mode 100644 docs/source/dsms.md create mode 100644 docs/source/dsms_config_schema.md create mode 100644 docs/source/dsms_kitem_schema.md create mode 100644 docs/source/dsms_sdk.md delete mode 100644 docs/source/installation.md delete mode 100644 docs/source/introduction.md delete mode 100644 environment_hiwi.txt delete mode 100644 examples/Chapters/1_introduction.ipynb delete mode 100644 examples/Chapters/2_creation.ipynb delete mode 100644 examples/Chapters/3_updation.ipynb delete mode 100644 examples/Chapters/4_deletion.ipynb delete mode 100644 examples/Chapters/5_search.ipynb delete mode 100644 examples/Chapters/6_app_HDF5.ipynb diff --git a/docs/describe.md b/docs/describe.md index 52d2abc..ef262b0 100644 --- a/docs/describe.md +++ b/docs/describe.md @@ -1,4 +1,3 @@ -""" This code is part of the dsms-python-sdk package located at D:\HiWi\dsms-python-sdk\dsms. The dsms-python-sdk package provides a set of tools and utilities for interacting with the DSMS (Data Science Management System) platform. It offers functionality for data ingestion, data processing, model training, and model deployment. @@ -11,8 +10,7 @@ The package is organized into the following packages: - `dsms.deploy`: Offers functionality for deploying trained models to the DSMS platform, including model packaging, versioning, and deployment management. Please refer to the individual package documentation for more details on their usage and available functionalities. -""" -""" + This module contains the implementation of the dsms-python-sdk package. The dsms-python-sdk package provides a set of tools and utilities for interacting with the DSMS (Data Science Management System) platform. @@ -27,18 +25,3 @@ Usage: ... For more information, please refer to the official documentation at https://github.com/username/dsms-python-sdk. -""" -# FILEPATH - -def calculate_sum(a, b): - """ - Calculates the sum of two numbers. - - Parameters: - a (int): The first number. - b (int): The second number. - - Returns: - int: The sum of the two numbers. - """ - return a + b diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 0000000..4f2a27a --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,5 @@ +/* Custom CSS for wrapping text in table cells */ +table td { + word-wrap: break-word; + white-space: normal !important; +} diff --git a/docs/source/assets/Dummy_Tensile_Test_Data.csv b/docs/source/assets/Dummy_Tensile_Test_Data.csv deleted file mode 100644 index 9a331bd..0000000 --- a/docs/source/assets/Dummy_Tensile_Test_Data.csv +++ /dev/null @@ -1,31 +0,0 @@ -Test ID,Material,Load (kN),Strain (%),Stress (MPa),Elongation (mm),Failure Mode -1,Steel,17.63,6.74,131.35,3.09,Brittle -2,Copper,7.93,2.18,326.25,3.16,Brittle -3,Aluminum,125.73,1.38,196.16,3.08,Mixed -4,Steel,117.83,3.22,336.91,1.5,Ductile -5,Copper,131.15,3.7,194.98,4.79,Ductile -6,Copper,146.9,5.74,354.39,2.51,Brittle -7,Copper,120.88,4.44,431.41,4.31,Ductile -8,Copper,71.91,9.88,151.32,3.65,Mixed -9,Aluminum,118.18,1.11,653.98,1.84,Mixed -10,Copper,22.15,2.17,553.28,4.16,Ductile -11,Aluminum,97.79,1.7,312.31,2.28,Mixed -12,Titanium,25.79,6.57,518.6,4.46,Mixed -13,Steel,141.98,2.61,175.15,3.12,Mixed -14,Copper,80.67,4.72,560.76,4.47,Brittle -15,Titanium,65.13,2.52,843.44,3.62,Brittle -16,Steel,43.36,1.67,354.86,3.76,Ductile -17,Steel,117.26,1.19,633.93,2.76,Ductile -18,Steel,71.14,6.6,205.44,4.8,Ductile -19,Titanium,87.42,1.47,673.06,3.4,Mixed -20,Aluminum,7.72,2.05,331.52,2.41,Mixed -21,Titanium,94.56,3.75,246.55,3.23,Brittle -22,Copper,93.75,8.23,569.21,0.59,Brittle -23,Copper,94.46,1.06,116.09,1.86,Ductile -24,Titanium,141.84,8.4,763.15,3.47,Ductile -25,Steel,103.86,1.05,103.76,1.81,Mixed -26,Aluminum,57.13,9.77,642.25,3.28,Brittle -27,Aluminum,68.37,4.74,316.01,2.43,Brittle -28,Aluminum,106.16,9.77,688.16,1.11,Mixed -29,Aluminum,13.73,6.09,869.75,1.84,Ductile -30,Steel,101.68,7.42,299.0,3.06,Brittle diff --git a/docs/source/assets/images/dsms_picture_01.jpg b/docs/source/assets/images/DSMS_Mod.jpg similarity index 100% rename from docs/source/assets/images/dsms_picture_01.jpg rename to docs/source/assets/images/DSMS_Mod.jpg diff --git a/docs/source/assets/images/dsms-sdk.jpg b/docs/source/assets/images/DSMS_SDK.jpg similarity index 100% rename from docs/source/assets/images/dsms-sdk.jpg rename to docs/source/assets/images/DSMS_SDK.jpg diff --git a/docs/source/assets/images/DSMS_logo.jpg b/docs/source/assets/images/DSMS_logo.jpg deleted file mode 100644 index 9925f67d31b63b69a4cea43e628379b13679d7fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91441 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�$*kTpm4?b<6W-{|T$ys96lJHuf%= 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 T$IdnreyXahsq|dIGVuQa3R_RP literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index ab7596e..714319f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,7 +16,7 @@ # -- Project information ----------------------------------------------------- -project = "DSMS-SDK Documentation" +project = "DSMS Documentation" copyright = "2024, Materials Informatics Team at Fraunhofer IWM" author = "Materials Informatics, Fraunhofer IWM" release = "v1.0.0" @@ -55,6 +55,10 @@ exclude_patterns = ["build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] +def setup(app): + app.add_css_file("custom.css") + + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/docs/source/dsms.md b/docs/source/dsms.md new file mode 100644 index 0000000..9bb7cba --- /dev/null +++ b/docs/source/dsms.md @@ -0,0 +1,31 @@ +## Intoduction to DSMS + +DSMS (acronym for Dataspace Management System) by Fraunhofer IWM is a web-based Data Managment platform that manages heterogeneous data and features using semantic and analytical capabilities. + +### 1.Introduction + +DSMS platform promotes and enables the provenance and catalogization of data through a combintation of classical relational databases and semantic technologies. By enabling the interoperabilty to third party data sources, Fraunhofer IWM demonstrates this though particular use cases in material science and manufacturing in public research projects for industry 4.0. + +![DSMS_Intro](assets/images/DSMS_Mod.jpg) + + +For the DSMS, Pydantic has been used extensively. Pydantic is a Python library that leverages type annotations for data validation and settings management. It ensures that data conforms to predefined schemas by validating or serializing it at runtime and thus facilitates strict type checking and data integrity. +In next chapters we look at the two most fundamental building blocks of DSMS which is KTypes and KItems. + +### 1.1. KItems and KTypes + +**What is a KType?** + +KType stands for knowledge type and categorizes types of knowledge instances and what kind of associated details is relevant for a particular KType (as shown in the attached image). It basically describes a concept and its schema. + +**What is a KItem?** + +KItem stands for knowledge item and represents an individual instance of a KType following its property schema and functionalities. Knowledge items also capture the concepts of data containers and digital twin. + +**How is it helpful?** + +This approach streamlines the schematisation, conceptualization and structurisation of data for various domains and applications. Technically speaking, it builds an ideal base for the integration of data and knowledge into large dataspaces. KItems classified through KTypes didactically, support the upscaling of information into knowledge graphs by additional semantic annotations - which are usually mapped by ontologists. + +KItems and KTypes embody the concepts of digital twins and data containers by providing a structured and semantic framework for representing real-world materials and processes in the manufacturing industry. + +![DSMS](assets/images/DSMS.jpg) diff --git a/docs/source/dsms_config_schema.md b/docs/source/dsms_config_schema.md new file mode 100644 index 0000000..fcaee7d --- /dev/null +++ b/docs/source/dsms_config_schema.md @@ -0,0 +1,60 @@ +## 4. DSMS Config Schema + +The `Configuration` class for the DSMS Python SDK is designed to handle various settings required to connect and interact with a DSMS instance. This documentation provides a detailed overview of the configurable properties, their types, defaults, and descriptions. + + +This section describes the configuration properties for the DSMS Python SDK. + +### Configuration Object Properties + +| Field Name | Description | Type | Default | Property Namespace | Required/Optional | +|:----------------:|:--------------------------------------------------------------------------------------------:|:--------------------:|:--------------------:|:------------------:|:-----------------:| +| host_url | URL of the DSMS instance to connect. | AnyUrl | Not Applicable | `host_url` | Required | +| request_timeout | Timeout in seconds until the request to the DSMS is timed out. | int | `30` | `request_timeout` | Optional | +| ssl_verify | Whether the SSL of the DSMS shall be verified during connection. | bool | `True` | `ssl_verify` | Optional | +| username | User name for connecting to the DSMS instance | Optional[SecretStr] | `None` | `username` | Optional | +| password | Password for connecting to the DSMS instance | Optional[SecretStr] | `None` | `password` | Optional | +| token | JWT bearer token for connecting to the DSMS instance | Optional[SecretStr] | `None` | `token` | Optional | +| ping_dsms | Check whether the host is a DSMS instance or not. | bool | `True` | `ping_dsms` | Optional | +| encoding | General encoding to be used for reading/writing serializations. | str | “utf-8” | `encoding` | Optional | +| datetime_format | Datetime format used in the DSMS instance. | str | “%Y-%m-%dT%H:%M:%S.%f” | `datetime_format` | Optional | +| kitem_repo | Repository of the triplestore for KItems in the DSMS | str | `knowledge` | `kitem_repo` | Optional | + +#### Example Usage +```python +from dsms.configuration import Configuration +from pydantic import SecretStr + +config = Configuration( + host_url="https://dsms.example.com", + request_timeout=30, + ssl_verify=True, + username=SecretStr("your_username"), + password=SecretStr("your_password"), + token=None, + ping_dsms=True, + individual_slugs=True, + encoding="utf-8", + datetime_format="%Y-%m-%dT%H:%M:%S.%f", + display_units=False, + autocomplete_units=True, + kitem_repo="knowledge-items", + qudt_units="http://qudt.org/2.1/vocab/unit", + qudt_quantity_kinds="http://qudt.org/vocab/quantitykind/", + units_sparql_object="dsms.knowledge.semantics.units.sparql:UnitSparqlQuery", + hide_properties={"external_links"} +) + +print(config) +``` + + +> **Reminder Tip** +> +> As discussed in previous section in DSMS SDK section (refer to the [Accessing DSMS Core](dsms_sdk.md#accessing-dsms-core)) for accessing DSMS core, you either need to pass during initialization: +> +> `username` and `password` +> +> OR +> +> `token` diff --git a/docs/source/dsms_kitem_schema.md b/docs/source/dsms_kitem_schema.md new file mode 100644 index 0000000..3e55513 --- /dev/null +++ b/docs/source/dsms_kitem_schema.md @@ -0,0 +1,187 @@ +## 3. DSMS KItem Schema + +A Kitem has several properties which enable it to handle data effectively. This section briefly describes the properties a Kitem can consist of, or in simple words, the schema of a KItem. + +The schema contains complex types and references, indicating an advanced usage scenario where various objects (like KItems and their properties) are interconnected. It also includes customizations like optional and default values, arrays of references, and conditional formats (e.g., UUID formats). + +The KItem object has the following properties + +### Kitem Object Properties + +![kitem_schema_uml](assets/images/UML_KItem_schema.jpg) + +| Field Name | Description | Type | Default | Property Namespace | Required / Optional | +|:-----------------:|:--------------------------------------------------------------------------------------------------------:|:-------------------------------------------------:|:--------:|:------------------:|:-----------------:| +| Name | Human-readable name of the KItem. | string | Not Applicable | `name` | Required | +| Slug | A unique slug identifier for the KItem, minimum of 4 characters. | string | `None` | `slug` | Optional | +| Ktype ID | The type ID of the KItem | Union[Enum, string] | Not Applicable | `ktype_id` | Required | +| Created At | Timestamp of when the KItem was created. | Union[string, datetime] | `None` | `created_at` | Automatically generated | +| Updated At | Timestamp of when the KItem was updated. | Union[string, datetime] | `None` | `updated_at` | Automatically generated | +| Avatar Exists | Whether the KItem holds an avatar or not. | boolean | `False` | `avatar_exists` | Automatically generated | +| Custom Properties | A set of custom properties related to the KItem. | Any | `{}` | `custom_properties`| Optional | +| Summary | A brief human-readable summary of the KItem | Union[string, [Summary](#summary-properties)] | `None` | `summary` | Optional | +| KItem Apps | A list of applications associated with the KItem | List[[App](#app-properties)] | `[ ]` | `kitem_apps` | Optional | +| Annotations | A list of annotations related to the KItem | List[[Annotation](#annotation-properties)] | `[ ]` | `annotations` | Optional | +| Affiliations | A list of affiliations associated with the KItem | List[[Affiliation](#affiliation-properties)] | `[ ]` | `affiliations` | Optional | +| Authors | A list of authors related to the KItem | List[Union[[Author](#author-properties), string]] | `[ ]` | `authors` | Optional | +| Contacts | Contact information related to the KItem | List[[ContactInfo](#contactinfo-properties)] | `[ ]` | `contacts` | Optional | +| External Links | A list of external links related to the KItem | List[[ExternalLink](#externallink-properties)] | `[ ]` | `external_links` | Optional | +| Attachments | A list of file attachments associated with the KItem | List [ Union [[Attachment ](#attachment-properties)], string] | `[ ]` | `attachments` | Optional | +| Hdf5 | HDF5 data structure associated with the KItem | Union[List[[Column](#column-properties)], pd.DataFrame, Dictionary[string, Union[List, Dictionary]]] | `None` | `hdf5` | Optional | +| Linked KItems | List of other KItems linked to this KItem | List[Union[[LinkedKItem](#linkedkitem-properties), "KItem"]] | `None` | `linked_kitems` | Optional | +| User Groups | User groups with access to this KItem | List[[UserGroup](#usergroup-properties)] | `[ ]` | `user_groups` | Optional | + +#### Example Usage +```python + +item = KItem( + name="Glass Bending machine 01", + slug="1234", + ktype_id="Testing Machine", + custom_properties={"location": "Room01", "max_force": "100Pa"}, + summary={"text": "This is a summary", "author": "John Doe"}, + kitem_apps=[{"executable": "tensile_analysis.py", "description": "analysis the tensile strength from machine data"}], + annotations=[{"iri": "http://example.org/sample_kitem/annotation"}], + affiliations=[{"name": "Fraunhofer IWM"}], + authors=[{"user_id": "Tom Brown"}], + contacts=[{"name": "John Doe", "email": "john.doe@example.com"}], + external_links=[{"label": "Project Website", "url": "https://example.com"}], + attachments=["research_data.csv"], + linked_kitems=[{"id": "{kitem_id_of_present}", "source_id": "{kitem_id_of_source}"}], + user_groups=[{"group_id": "{other_kitem_id}", "name": "DigiMaterials"}] +) +``` + +### Summary Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| Text | Summary | string | `None` | `text` | Required | +| Author | Author of the summary | string | `None` | `author` | Required | + +#### Example Usage +```python +sample_kitem.summary = {"text": "This is a summary", "author": "John Doe"} +``` + +### App Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| Executable | Name of the executable | string | `None` | `executable` | Required | +| Description | Description of the application | string | `None` | `description` |Required | + +#### Example Usage +```python +sample_kitem.summary = {"executable": "unit_conversion.py", "description": "converting input to different units"} +``` + +### Annotation Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| IRI | IRI of the annotation | string | `None` | `iri` | Required | +| Name | Name of the annotation | string | `None` | `name` | Required | +| Namespace | Namespace of the annotation | string | `None` | `namespace` | Required | + +#### Example Usage +```python +sample_kitem.annotation = {"iri": "1238.py", "name": "","namespace":""} +``` + +### Affiliation Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| Name | Name of the affiliation | string | `None` | `name` | Required | + +#### Example Usage +```python +sample_kitem.affiliation = {"name": "Research BAC"} +``` + +### Author Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| +| User Id | ID of the DSMS User | string (UUID) | `None` | `user_id` | Required | + +#### Example Usage +```python +sample_kitem.author = {"user_id": "1238"} +``` + +### ContactInfo Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| +| Email | Email of the contact person | string | `None` | `email` | Required | +| Name | Name of the contact person | string | `None` | `name` | Required | +| User Id | User ID of the contact person | string (UUID) | `None` | `user_id` | Optional | + +#### Example Usage +```python +sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3"} +``` + +### ExternalLink Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------------------------:|:-------:|:------------------:|:-----------------:| +| Label | Label of the external link | string | `None` | `label` | Required | +| Url | URL of the external link | string , format: URI, minLength: 1 | `None` | `url` | Required | + +#### Example Usage +```python +sample_kitem.externallink = {"label": "project link","url": "www.projectmachine01.com"} +``` + + +### Attachment Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| Name | File name of the attachment | string | `None` | `name` | Required | + +#### Example Usage +```python +sample_kitem.attachment = {"strength.csv": "strength test results data"} +``` + + +### Column Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| Name | File name of the attachment | string | `None` | `name` | Required | + +#### Example Usage +```python +sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3"} +``` + + +### LinkedKItem Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| +| Id | ID of the KItem to be linked | string (UUID) | `None` | `id` | Required | +| Source Id | Source Id of the KItem which has been linked | string (UUID) | `None` | `source_id` | Required | + +#### Example Usage +```python +sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} +``` + +### UserGroup Properties + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| +| Id | KItem ID related to the KItem property | string (UUID) | `None` | `id` | Required | +| Group Id | ID of the user group | string | `None` | `group_id` | Required | +| Name | Name of the user group | string | `None` | `name` | Required | + +#### Example Usage +```python +sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} +``` diff --git a/docs/source/dsms_sdk.md b/docs/source/dsms_sdk.md new file mode 100644 index 0000000..1b4822a --- /dev/null +++ b/docs/source/dsms_sdk.md @@ -0,0 +1,163 @@ + +# About DSMS-SDK + +## 1. Overview + +In the dynamic world of Material Science, the introduction of our latest development brings a new way of interaction with our Dataspace Management System (DSMS): The DSMS Python SDK. + +**What is the DSMS SDK?** + +SDK stands for Software Development Kit. In our case, it is a Python-based package for the interaction with the DSMS. This means that all fundamental functionalities of the DSMS can be accessed through a Python interface now! + +**How does the SDK work?** + +Just install it on your local system via the pip command line interface: + +```bash +pip install dsms-sdk +``` + +... and start connecting to your central DSMS instance remotely, e.g. by integrating it into your own Python scripts and packages + +The SDK functionalities are listed below: +1. Managing Knowledge-Items. +2. Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test. +3. Administrating authorship, contact information and supplementary information. +4. Semantic annotation of K-Items. +5. Conduct simple free-text searches and SPARQL queries. +6. Linking K-Items to other K-Items. +7. Linking Apps to K-Items, triggered, for example, during a file upload. +8. Performing simple file upload and download of file attachments. +9. Export of a knowledge (sub) graph into TTL/JSON-LD. + + +Click on the link to go to the Github repository of the Python based DSMS-SDK : [Git repo](https://github.com/MI-FraunhoferIWM/dsms-python-sdk) + +![dsms-sdk](assets/images/DSMS_SDK.jpg) + +## 2. Installation + +### Installation and Setup Guide + +How to install and setup the dsms-python-sdk. + +### Pre-Requisites + +Before using DSMS-SDK make sure to register an account in a DSMS Instance of your choice. You could do this by sending an email to the following contacts: + +- [Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) +- [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) + +After following the above steps, you could use either of the two ways to install DSMS-SDK to your machine. + +#### Method 1: Via PyPI + +To install the [DSMS Python SDK](https://pypi.org/project/dsms-sdk/), you can use the Python Package Index (PyPI). Copy the below command to install DSMS SDK from PyPI: + +```bash +pip install dsms-sdk +``` + +#### Method 2: Cloning Github Repo + +Download and install on th8e local machine : + +```bash +git clone git@github.com:MI-FraunhoferIWM/dsms-python-sdk.git +cd dsms-python-sdk +pip install . +``` + +### Accessing DSMS Core + +You need to authenticate yourself to connect with dsms-core using dsms-python-sdk which can be done by following one of the below given steps: + +1. **Pick the DSMS host of your choice.** + + The following are the instances of the DSMS you could choose from: + + - [StahlDigital](https://lnkd.in/gfwe9a36) + - [KupferDigital](https://lnkd.in/g8mvnM3K) + - [DiMAT](https://lnkd.in/g46baB6J) + +2. **Authentication Options** + + You can log into the DSMS core using one of the following methods: + + **a.** **Pass as Keyword Arguments** + + Directly pass your credentials (stored in a .env file) as keyword arguments when initializing the SDK. + + ```python + from dsms_sdk import DSMS + dsms = DSMS(env="../.env") + ``` + + Then add the relevant information in the .env file for setting up the login with DSMS core via SDK. Here, we choose the stahldigital instance for demo: + + ``` + DSMS_HOST_URL = https://stahldigital.materials-data.space + DSMS_USERNAME = {YOUR_USERNAME} + DSMS_PASSWORD = {YOUR_PASSWORD} + ``` + + Now save the file and the user is ready to connect with DSMS-Core via SDK. + + **b.** **Pass Config Variables while Initialization** + + Use configuration variables during initialization. Use the `getpass` module for securely entering your password. + + ```python + import getpass + from dsms_sdk import DSMS + dsms_host = "https://stahldigital.materials-data.space" + dsms_username = input("Enter your username: ") + dsms_password = getpass.getpass("Enter your password: ") + + dsms = DSMS(host=dsms_host, username=dsms_username, password=dsms_password) + ``` + + **c. Using Frontend and get token**: + + The user can login in the DSMS instance of choice and then use the login token, which can be obtained by following the below steps from the DSMS frontend interface. + + Step 1: Login into the selected dataspace instance of your choice. + + ![copy_token_1](assets/images/copy_token_1.jpg) + + Step 2: Enter your credentials + + ![copy_token_2](assets/images/copy_token_2.jpg) + + Step 3: After logging in you will land up on the home page. Now proceed to the my profile option + + ![copy_token_3](assets/images/copy_token_3.jpg) + + Step 4: The my profile option should look something like below. Now click on Advanced. + + ![copy_token_4](assets/images/copy_token_4.jpg) + + Step 5: After landing up on Advanced section, click on the copy token to clipboard + + ![copy_token_6](assets/images/copy_token_5.jpg) + + Step 6: Now the login token is in the clipboard. Now paste the copied login token from the frontend in the DSMS_TOKEN attribute of the .env file + ``` + DSMS_HOST_URL = https://stahldigital.materials-data.space + DSMS_TOKEN = {YOUR_COPIED_TOKEN} + ``` + +Now you are ready to use dsms-sdk. Do check out the tutorials section to try out some basic examples on how to use dsms-sdk. + +The next sections covers about the schema of fundamental classes crucial for users to know about DSMS when using the platform. Below given explains about the Schema of `KItem` and its associated properties in DSMS. + + + +```{include} dsms_kitem_schema.md +``` + +Now the next section gives a brief explanation about the Schema of `Config` class of DSMS and its associated properties in DSMS. + + +```{include} dsms_config_schema.md +``` diff --git a/docs/source/index.md b/docs/source/index.md index 4e500fd..5db57f9 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,29 +1,47 @@ -# DSMS SDK Documentation +# DSMS Documentation -Welcome to the DSMS SDK documentation! +Welcome to documentation of DSMS! -Here you will find all the information regarding the installation, setup and basic usage of the DSMS python SDK. +Here you will find all the information regarding the installation, setup and basic usage of the DSMS and the python based DSMS-SDK. ````{panels} :body: text-center. --- -**Installation** +**DSMS** -Quick-setup of the DSMS-SDK +Introduction to DSMS -```{link-button} installation.html -:text: Install the SDK! +```{link-button} dsms.html +:text: About DSMS :classes: btn-outline-primary stretched-link --- -**Introduction** +**DSMS-SDK** - Overview of the DSMS-SDK. + Overview of the DSMS-SDK -```{link-button} introduction.html -:text: Basics of the SDK +```{link-button} dsms_sdk.html +:text: Basics of DSMS-SDK +:classes: btn-outline-primary stretched-link + +--- +**DSMS KITEM SCHEMA** + +Overview of DSMS KItem Schema + +```{link-button} dsms_kitem_schema.html +:text: DSMS KItem Schema +:classes: btn-outline-primary stretched-link + +--- +**DSMS CONFIG SCHEMA** + +Overview of DSMS Config Schema + +```{link-button} dsms_config_schema.html +:text: DSMS Config Schema :classes: btn-outline-primary stretched-link --- @@ -46,8 +64,8 @@ Feel free to report any issues/missing information so we can take a look into it :caption: Table of contents :maxdepth: 1 -installation -introduction +dsms +dsms_sdk Tutorials ``` diff --git a/docs/source/installation.md b/docs/source/installation.md deleted file mode 100644 index 5aaf60b..0000000 --- a/docs/source/installation.md +++ /dev/null @@ -1,81 +0,0 @@ -# Installation and Setup Guide -How to install and setup the dsms-python-sdk. - -## 1.1. Installation - -Before using DSMS-SDK make sure to register your account at your DSMS Instance.You could do this by sending an email to the following contacts: - -- [Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) -- [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) - - -After following the above steps, you could use either of the two ways to install DSMS to your machine. - -#### Method 1 : Via PyPI #### - -To install the [DSMS Python SDK](https://pypi.org/project/dsms-sdk/), you can use the Python Package Index (PyPI). -Copy the below command to install DSMS SDK from PyPI -- `pip install dsms-sdk` - - -#### Method 2 : Cloning Github Repo #### - -Install in your folder (or any folder you like). - -- `git clone git@github.com:MI-FraunhoferIWM/dsms-python-sdk.git` -- `cd dsms-python-sdk` -- `pip install .` - -## 1.2. Additional Setup -You need to authenticate yourself to connect with dsms-core using dsms-python-sdk. - -Therefore follow the following process: -1. **Pick the DSMS host of your choice.** - - The following are the instances of the DSMS you could choose from: - -- [StahlDigital](https://lnkd.in/gfwe9a36) -- [KupferDigital](https://lnkd.in/g8mvnM3K) -- [DiMAT](https://lnkd.in/g46baB6J) - -2. **Add the relevant information in the .env file for setting up the login with DSMS core via SDK.** - - Step 1 : Create a .env file in the following folders - - a. {path_to_dsms-python-sdk}/dsms-python-sdk/.env - - b. {path_to_dsms-python-sdk}/dsms-python-sdk/docs/source/.env - - Step 2 : Populate both the .env file with the following information. Here, we choose the stahldigital instance for demo : - - DSMS_HOST_URL = https://stahldigital.materials-data.space - DSMS_USERNAME = {YOUR_USERNAME} - DSMS_PASSWORD = {YOUR_PASSWORD} - - Now Save the file and the user is ready to connect with DSMS-Core via SDK. - -**3. How to get the token [Optional]** - - Step 1: Login into the selected data.space instance of your choice. - -![copy_token_1](assets/images/copy_token_1.jpg) - - Step 2: Enter your credentials - -![copy_token_2](assets/images/copy_token_2.jpg) - - Step 3: After logging in you will land up in the home page. Now look at the my profile option - -![copy_token_3](assets/images/copy_token_3.jpg) - - Step 4: The my profile option should look something like below. Now click on the Advanced. - -![copy_token_4](assets/images/copy_token_4.jpg) - - Step 5: The Advanced section should like the below. Now click on the copy token to clipboard - -![copy_token_6](assets/images/copy_token_5.jpg) - - Step 6: Now paste it in DSMS_TOKEN attribute of the .env file in root/dsms-python-sdk. - -Now you are ready to use dsms-sdk. Do check out the tutorials section to try out on some basic examples on how to use dsms-sdk. diff --git a/docs/source/introduction.md b/docs/source/introduction.md deleted file mode 100644 index 6919657..0000000 --- a/docs/source/introduction.md +++ /dev/null @@ -1,413 +0,0 @@ -# Introduction to DSMS-SDK -## 1.1. Overview - -In the dynamic world of Material Science, the introduction of our latest development brings a new way of interaction with our Dataspace Management System (DSMS): The DSMS Python SDK. - -**What is the DSMS SDK?** - -SDK stands for Software Development Kit. In our case, it is a Python-based package for the interaction with the DSMS. This means that all fundamental functionalities of the DSMS can be accessed through a Python interface now! - -**How does the SDK work?** - -Just install it on your local system via the pip command line interface: - -`pip install dsms-sdk` - -... and start connecting to your central DSMS instance remotely, e.g. by integrating it into your own Python scripts and packages - -The SDK functionalities are listed below: -1. Managing Knowledge-Items. -2. Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test. -3. Administrating authorship, contact information and supplementary information. -4. Semantic annotation of K-Items. -5. Conduct simple free-text searches and SPARQL queries. -6. Linking K-Items to other K-Items. -7. Linking Apps to K-Items, triggered, for example, during a file upload. -8. Performing simple file upload and download of file attachments. -9. Export of a knowledge (sub) graph into TTL/JSON-LD. -[Git repo](https://github.com/MI-FraunhoferIWM/dsms-python-sdk) - -![dsms-sdk](assets/images/dsms-sdk.jpg) - - -## 1.2. DSMS - -DSMS (acronym for Dataspace Management System) by Fraunhofer is a web-based application that manages heterogeneous data and features semantic and analytical capabilities. - -### 1.2.1. Introduction - -DSMS platform promotes and enables the provenance and catalogization of data through a combintation of classical relational databases and semantic technologies. By enabling the interoperabilty to third party data sources, Fraunhofer IWM demonstrates this though particular use cases in material science and manufacturing in public research projects for industry 4.0. - -![DSMS_Intro](assets/images/dsms_picture_01.jpg) - - -### 1.2.2. KItems and KTypes - -**What is K-type?** - -K-type stands for knowledge type and categorizes types of knowledge instances and what kind of details are relevant for them (as shown in the attached image). It basically describes a concept and its schema. - -**What is K-item?** - -K-item stands for knowledge item and represents an individual instance of a k-type following its property schema and functionalities. Knowledge items also capture the concepts of data containers and digital twin. - -**How is it helpful?** - -This approach streamlines the schematisation, conceptualization and structurisation of data for various domains and applications. Technically speaking, it builds an ideal base for the integration of data and knowledge into large dataspaces. K-Items classify through K-Types didactically, support the upscaling of information into knowledge graphs by additional semantic annotations - which are usually mapped by ontologists. - -K-Items and K-Types embody the concepts of digital twins and data containers by providing a structured and semantic framework for representing real-world materials and processes in the manufacturing industry. - -![DSMS](assets/images/DSMS.jpg) - -### 1.2.3. Kitem Properties - -A Kitem has several properties to enable to handle data effectively. This section briefly describes the properties a Kitem can consist or in simple words the schema of a K-item. - -The schema contains complex types and references, indicating an advanced usage scenario where various objects (like KItems and their properties) are interconnected. It also includes customizations like optional and default values, arrays of references, and conditional formats (e.g., UUID formats). - -For the DSMS, Pydantic has been used extensively. Pydantic is a Python library that leverages type annotations for data validation and settings management. It ensures that data conforms to predefined schemas by validating or serializing it at runtime and thus facilitates strict type checking and data integrity. - -Below given is the details of the schema of a Kitem for better understanding: - -### 1.2.3. Kitem Properties - -A Kitem has several properties to enable to handle data effectively. This section briefly describes the properties a Kitem can consist or in simple words the schema of a K-item. - -The schema contains complex types and references, indicating an advanced usage scenario where various objects (like KItems and their properties) are interconnected. It also includes customizations like optional and default values, arrays of references, and conditional formats (e.g., UUID formats). - -For the DSMS, Pydantic has been used extensively. Pydantic is a Python library that leverages type annotations for data validation and settings management. It ensures that data conforms to predefined schemas by validating or serializing it at runtime and thus facilitates strict type checking and data integrity. - -Below given is the details of the schema of a Kitem for better understanding: - - -# DSMS Schema Documentation - -**Note:** This documentation covers the various components within the DSMS schema, focusing on the properties, types, and roles of different objects. - -## Definitions - -### General Definitions - -All IDs are typically of the type `UUID` unless specified otherwise. Each entity defined within the system has an associated ID which is automatically generated. - -### 1. KItem - -**Description:** Represents a Knowledge Item within the DSMS system. - -**Required Fields:** `name`, `ktype_id` - -#### KItem Properties - -1.1. **Name** -- **Description:** Human-readable name of the KItem. -- **Type:** string -- **Default:** - - -1.2. **Slug** -- **Description:** A unique slug identifier for the KItem, minimum of 4 characters. -- **Type:** string, MinLength: 4 -- **Default:** None - -1.3. **Ktype_id** -- **Description:** The type ID of the KItem. -- **Type:** string or enumeration -- **Default:** - - -1.4. **Created_at** -- **Description:** Timestamp of when the KItem was created. -- **Type:** string, format: date-time -- **Default:** None - -1.5. **Updated_at** -- **Description:** Timestamp of when the KItem was last updated. -- **Type:** string, format: date-time -- **Default:** None - -1.6. **Summary** -- **Description:** A brief human-readable summary of the KItem. -- **Type:** string or summary object -- **Default:** None - -1.7. **Avatar_exists** -- **Description:** Indicates whether the KItem has an avatar image associated with it. -- **Type:** boolean -- **Default:** False - -1.8. **Custom_properties** -- **Description:** A set of custom properties related to the KItem. -- **Type:** object -- **Default:** {} - -1.9. **Kitem_apps** -- **Description:** A list of applications associated with the KItem. -- **Type:** array of App objects -- **Default:** [] - -1.10. **Annotations** -- **Description:** A list of annotations related to the KItem. -- **Type:** array of Annotation objects -- **Default:** [] - -1.11. **Affiliations** -- **Description:** A list of affiliations associated with the KItem. -- **Type:** array of Affiliation objects -- **Default:** [] - -1.12. **Authors** -- **Description:** A list of authors related to the KItem. -- **Type:** array of Author objects -- **Default:** [] - -1.13. **Contacts** -- **Description:** Contact information related to the KItem. -- **Type:** array of ContactInfo objects -- **Default:** [] - -1.14. **External_links** -- **Description:** A list of external links related to the KItem. -- **Type:** array of ExternalLink objects -- **Default:** [] - -1.15. **Attachments** -- **Description:** A list of file attachments associated with the KItem. -- **Type:** array of Attachment objects or strings -- **Default:** [] - -1.16. **Hdf5** -- **Description:** HDF5 data structure associated with the KItem. -- **Type:** array of Column objects or object -- **Default:** None - -1.17. **Linked_kitems** -- **Description:** List of other KItems linked to this KItem. -- **Type:** array of LinkedKItem or KItem objects -- **Default:** [] - -1.18. **User_groups** -- **Description:** User groups with access to this KItem. -- **Type:** array of UserGroup objects -- **Default:** [] - -### 2. App - -**Description:** Represents an application associated with a KItem. - -**Required Fields:** `executable` - -#### App Properties - -2.1. **Executable** -- **Description:** Name of the executable related to the app. -- **Type:** string -- **Default:** - - -2.2. **Description** -- **Description:** Description of the application. -- **Type:** string or null -- **Default:** None - -2.3. **KitemAppId** -- **Description:** ID of the KItem App. -- **Type:** integer or null -- **Default:** None - -2.4. **Tags** -- **Description:** Tags related to the application. -- **Type:** object or null -- **Default:** None - -2.5. **Title** -- **Description:** Title of the application. -- **Type:** string or null -- **Default:** None - -2.6. **Additional Properties** -- **Description:** Additional properties related to the application. -- **Type:** Refers to `AdditionalProperties` -- **Default:** None - -### 3. Affiliation - -**Description:** Represents an affiliation associated with a KItem. - -**Required Fields:** `Name` - -#### Affiliation Properties - -3.1. **Name** -- **Description:** Name of the affiliation. -- **Type:** string -- **Default:** - - -### 4. Annotation - -**Description:** Represents an annotation within a KItem. - -**Required Fields:** `iri`, `name`, `namespace` - -#### Annotation Properties - -4.1. **Iri** -- **Description:** IRI of the annotation. -- **Type:** string -- **Default:** - - -4.2. **Name** -- **Description:** Name of the annotation. -- **Type:** string -- **Default:** - - -4.3. **Namespace** -- **Description:** Namespace of the annotation. -- **Type:** string -- **Default:** - - -### 5. Attachment - -**Description:** Represents a file attachment uploaded by a certain user. - -**Required Fields:** `name` - -#### Attachment Properties - -5.1. **Name** -- **Description:** File name of the attachment. -- **Type:** string -- **Default:** - - -### 6. Author - -**Description:** Represents an author of a KItem. - -**Required Fields:** `UserID` - -#### Author Properties - -6.1. **UserId** -- **Description:** ID of the DSMS User. -- **Type:** string (UUID) -- **Default:** - - -### 7. Column - -**Description:** Represents a column of an HDF5 data frame. - -**Required Fields:** `ColumnId`, `Name` - -#### Column Properties - -7.1. **ColumnId** -- **Description:** Column ID in the data frame. -- **Type:** integer -- **Default:** - - -7.2. **Name** -- **Description:** Name of the column in the data series. -- **Type:** string -- **Default:** - - -### 8. ContactInfo - -**Description:** Contact information for a person or entity. - -**Required Fields:** `name`, `email` - -#### ContactInfo Properties - -8.1. **Email** -- **Description:** Email of the contact person. -- **Type:** string -- **Default:** - - - -8.2. **Name** -- **Description:** Name of the contact person. -- **Type:** string -- **Default:** - - -8.3. **UserId** -- **Description:** User ID of the contact person. -- **Type:** string (UUID) or null -- **Default:** None - -### 9. ExternalLink - -**Description:** Represents an external link of a KItem. - -**Required Fields:** `label`, `Url` - -#### ExternalLink Properties - -9.1. **Label** -- **Description:** Label of the external link. -- **Type:** string -- **Default:** - - -9.2. **Url** -- **Description:** URL of the external link. -- **Type:** string, format: uri, minLength: 1 -- **Default:** - - -### 10. LinkedKItem - -**Description:** Data model for a linked KItem. - -**Required Fields:** None - -#### LinkedKItem Properties - -10.1. **Id** -- **Description:** ID of the KItem to be linked. -- **Type:** string (UUID) or null -- **Default:** None - -10.2. **SourceId** -- **Description:** Source ID of the KItem. -- **Type:** string (UUID) or null -- **Default:** None - -### 11. Summary - -**Description:** Model for the custom properties of the KItem. - -**Required Fields:** `text` - -#### Summary Properties - -11.1. **Id** -- **Description:** KItem ID. -- **Type:** string (UUID) or null -- **Default:** None - -11.2. **Kitem** -- **Description:** KItem related to the summary. -- **Type:** null or object -- **Default:** None - -11.3. **Text** -- **Description:** Summary text of the KItem. -- **Type:** string -- **Default:** - - -### 12. UserGroup - -**Description:** User groups related to a KItem. - -**Required Fields:** `GroupId`, `name` - -#### UserGroup Properties - -12.1. **GroupId** -- **Description:** ID of the user group. -- **Type:** string -- **Default:** - - -12.2. **Id** -- **Description:** KItem ID related to the KPropertyItem. -- **Type:** string (UUID) or null -- **Default:** None - -12.3. **Name** -- **Description:** Name of the user group. -- **Type:** string -- **Default:** - diff --git a/docs/source/tutorials/1_introduction.ipynb b/docs/source/tutorials/1_introduction.ipynb index 514b1cf..e3ea410 100644 --- a/docs/source/tutorials/1_introduction.ipynb +++ b/docs/source/tutorials/1_introduction.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 1. Introduction to the DSMS-SDK\n", + "# Tutorial 1: Introduction to the DSMS-SDK\n", "\n", "In this tutorial we see the overview on how to setup and basic use DSMS-SDK\n", "\n" @@ -15,7 +15,7 @@ "metadata": {}, "source": [ "### 1.1. Setting up\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] @@ -40,20 +40,7 @@ "cell_type": "code", "execution_count": 2, "metadata": {}, - "outputs": [ - { - "ename": "OSError", - "evalue": "File `../.env` does not exist", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mOSError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[2], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m dsms \u001b[38;5;241m=\u001b[39m \u001b[43mDSMS\u001b[49m\u001b[43m(\u001b[49m\u001b[43menv\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m../.env\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dsms-python-sdk/dsms/core/dsms.py:79\u001b[0m, in \u001b[0;36mDSMS.__init__\u001b[0;34m(self, config, env, **kwargs)\u001b[0m\n\u001b[1;32m 77\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m env:\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mexists(env):\n\u001b[0;32m---> 79\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFile `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00menv\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m` does not exist\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 80\u001b[0m loaded \u001b[38;5;241m=\u001b[39m load_dotenv(env, verbose\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[1;32m 81\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m loaded:\n", - "\u001b[0;31mOSError\u001b[0m: File `../.env` does not exist" - ] - } - ], + "outputs": [], "source": [ "dsms = DSMS(env=\"../.env\")" ] diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index 49d2d62..52ada9d 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 2. Create Kitems\n", + "# Tutorial 2: Create Kitems\n", "\n", "In this tutorial we see how to create new Kitems." ] @@ -14,14 +14,14 @@ "metadata": {}, "source": [ "### 2.1. Setting up\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -48,22 +48,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now display what kitems we have" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "\n", "### 2.2: Create KItems" ] }, @@ -71,15 +56,81 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can make new KItems by simple class-initiation: (Make Sure existing KItems is not put as input). \n", + "We can make new KItems by simple class-initiation: (Make sure existing KItems are not given as input). \n", "#" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = d377511c-8086-466a-9972-2e4e002f8b18, \n", + "\n", + "\tktype_id = KTypes.TestingMachine, \n", + "\n", + "\tin_backend = False, \n", + "\n", + "\tslug = machine-1-d377511c, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = None, \n", + "\n", + "\tupdated_at = None, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tsupplier: None, \n", + "\t\tmaximumforce: None, \n", + "\t\tmaximumspeed: None, \n", + "\t\tlocation: None, \n", + "\t\tcalibrationcomponents: None, \n", + "\t\tcalibrationstatus: None, \n", + "\t\tinventorynumber: None, \n", + "\t\tserialnumber: None, \n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tRoom Number: A404, \n", + "\t\tDescription: Bending Test Machine\n", + "\t}, \n", + "\n", + "\thdf5 = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "item = KItem(\n", " name=\"Machine-1\",\n", @@ -102,9 +153,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'https://stahldigital.materials-data.space/knowledge/testing-machine/machine-1-d377511c'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "dsms.commit()\n", "item.url" @@ -114,34 +176,156 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As we can see, the object we created before running the `commit`-method has automatically been updated, e.g. with the creation- and update-timestamp:" + "As we can see, the object we created before running the `commit`-method has automatically been updated, e.g. with the creation- and update-timestamp. We can check this with the below command:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = d377511c-8086-466a-9972-2e4e002f8b18, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-d377511c, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: subgraph.ttl\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 9c8a295a-aeed-46c1-87f1-2793ef78320b\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-06-27 14:01:23.084258, \n", + "\n", + "\tupdated_at = 2024-06-27 14:01:23.084258, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tsupplier: None, \n", + "\t\tmaximumforce: None, \n", + "\t\tmaximumspeed: None, \n", + "\t\tlocation: None, \n", + "\t\tcalibrationcomponents: None, \n", + "\t\tcalibrationstatus: None, \n", + "\t\tinventorynumber: None, \n", + "\t\tserialnumber: None, \n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tRoom Number: A404, \n", + "\t\tDescription: Bending Test Machine\n", + "\t}, \n", + "\n", + "\thdf5 = None, \n", + "\n", + "\trdf_exists = True\n", + ")" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "item\n", - "item.name\n", - "type(item)" + "item" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "And the list of our KItems in the DSMS has been updated as well:" + "To just get the name of the item, we can do it as follows:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'Machine-1'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "dsms.kitems" + "item.name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To check the type of the item newly created, can be done as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dsms.knowledge.kitem.KItem" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now you can check if the particular kitem is in the list of KItems. This can be done either by using the command:\n", + " `\n", + " dsms.kitems\n", + " `\n", + " or by logging into the frontend dsms instance." ] } ], diff --git a/docs/source/tutorials/3_updation.ipynb b/docs/source/tutorials/3_updation.ipynb index f447cfa..8f9e6aa 100644 --- a/docs/source/tutorials/3_updation.ipynb +++ b/docs/source/tutorials/3_updation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 3. Updating KItems\n", + "# Tutorial 3: Updating KItems\n", "\n", "In this tutorial we see how to update existing Kitems." ] @@ -14,14 +14,15 @@ "metadata": {}, "source": [ "### 3.1. Setting up\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", + "\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -37,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -48,23 +49,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now display what kitems we have" + "Now lets get the kitem we created in the [2nd tutorial : Creation of Kitems](2_creation.ipynb)\n" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Machine-1\n" + ] + } + ], "source": [ "item = dsms.kitems[0]\n", "print(item.name)" @@ -83,15 +83,14 @@ "source": [ "Now, we would like to update the properties of our KItem we created previously.\n", "\n", - "Depending on the schema of each property (see `KItem.model_schema_json()` in the **Introduction** of this tutorial), we can simply use the standard `list`-method as we know them from basic Python (e.g. for the `annotations`, `attachments`, `external_link`, etc). \n", - "\n", + "Depending on the schema of each property (see [DSMS KItem Schema](../dsms_kitem_schema.md)), we can simply use the standard `list`-method as we know them from basic Python (e.g. for the `annotations`, `attachments`, `external_link`, etc). \n", "\n", "Other properties which are not `list`-like can be simply set by attribute-assignment (e.g. `name`, `slug`, `ktype_id`, etc)." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -109,7 +108,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -120,7 +119,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can see now that e.g. the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have beem thrown during the `commit`." + "We can see now that the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have been thrown during the `commit`." ] }, { @@ -132,9 +131,69 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t\t\t Downloaded file: {\n", + "\t\t\tname: README.md\n", + "\t\t}\n", + "|------------------------------------Beginning of file------------------------------------|\n", + "# DSMS-SDK\n", + "Python SDK core-package for interacting with the Dataspace Management System (DSMS)\n", + "\n", + "\n", + "## Authors\n", + "\n", + "[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", + "\n", + "[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", + "\n", + "[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", + "\n", + "## License\n", + "\n", + "This project is licensed under the BSD 3-Clause. See the LICENSE file for more information.\n", + "\n", + "## Usage\n", + "\n", + "The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities:\n", + "\n", + "- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType)\n", + " - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test\n", + " - Administrating authorship, contact information and supplementary information upon making changes or adding KItems\n", + " - Semantic annotation of KItems\n", + "- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface\n", + "- Linking KItems to other KItems\n", + "- Linking Apps to KItems, triggererd, for example, during a file upload\n", + "- Performing simple file upload and download using attachments to KItems\n", + "- Export of a knowledge (sub) graph as common serializations (.ttl, .json)\n", + "\n", + "For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items.\n", + "\n", + "\n", + "## Disclaimer\n", + "\n", + "Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM.\n", + "\n", + "Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de)\n", + "\n", + "|---------------------------------------End of file---------------------------------------|\n", + "\t\t\t Downloaded file: {\n", + "\t\t\tname: subgraph.ttl\n", + "\t\t}\n", + "|------------------------------------Beginning of file------------------------------------|\n", + "\n", + " a .\n", + "\n", + "\n", + "|---------------------------------------End of file---------------------------------------|\n" + ] + } + ], "source": [ "for file in item.attachments:\n", " download = file.download()\n", diff --git a/docs/source/tutorials/4_deletion.ipynb b/docs/source/tutorials/4_deletion.ipynb index 5e7cbcb..16ff2e3 100644 --- a/docs/source/tutorials/4_deletion.ipynb +++ b/docs/source/tutorials/4_deletion.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 4. Deleting KItems\n", + "# Tutorial 4: Deleting KItems\n", "\n", "In this tutorial we see how to delete new Kitems and their properties." ] @@ -15,7 +15,7 @@ "source": [ "### 4.1. Setting up\n", "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] @@ -49,16 +49,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now display what kitems we have" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" + "Then lets see the Kitem we are interested in to remove." ] }, { @@ -172,6 +163,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "\n", "Commit the changes:" ] }, @@ -188,16 +180,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As we can see know, our KItem has been removed from our inventory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" + "Now to check if the particular kitem was removed, we can do this by using the command:\n", + " `\n", + " dsms.kitems\n", + " `\n", + " or by logging into the frontend dsms instance." ] } ], diff --git a/docs/source/tutorials/5_search.ipynb b/docs/source/tutorials/5_search.ipynb index f65c895..3268951 100644 --- a/docs/source/tutorials/5_search.ipynb +++ b/docs/source/tutorials/5_search.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 5. Searching Kitems\n", + "# Tutorial 5: Searching Kitems\n", "\n", "In this tutorial we see how to search existing Kitems" ] @@ -15,7 +15,7 @@ "source": [ "### 5.1. Setting up\n", "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] @@ -45,22 +45,6 @@ "dsms = DSMS(env=\"../.env\")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now display what kitems we have" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -72,7 +56,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the last unit of this tutorial, we would like to search for specfic KItems we created in the DSMS.\n", + "In this section, we would like to search for specfic KItems we created in the DSMS.\n", "\n", "For this purpose, we will firstly create some KItems and apply the `search`-method on the `DSMS`-object later on in order to find them again in the DSMS.\n", "\n", @@ -131,9 +115,9 @@ " linked_kitems=[item1],\n", " annotations=[\n", " {\n", - " \"iri\": \"www.fraunhoferBACiri.org/foo\",\n", - " \"name\": \"fraunhoferBACInstitute\",\n", - " \"namespace\": \"www.fraunhoferBAC.org\",\n", + " \"iri\": \"www.researchBACiri.org/foo\",\n", + " \"name\": \"research BAC Institute\",\n", + " \"namespace\": \"research-BAC\",\n", " }\n", " ],\n", ")\n", @@ -145,16 +129,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, we are apply to search for e.g. kitems of type `DatasetCatalog`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.TestingMachine])" + "\n", + "####

    Note : Here in this tutorial, we use dsms.search with `limit=1` to make it aesthically precise and maintain easy readability but the user can adjust the variable `limit` as per requirement.

    \n", + "\n", + "\n", + "Now, we are apply to search for e.g. kitems of type `TestingMachine`:" ] }, { @@ -163,7 +142,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsms.kitems" + "dsms.search(ktypes=[dsms.ktypes.TestingMachine], limit=1)" ] }, { @@ -179,7 +158,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog], limit=1)" ] }, { @@ -195,14 +174,14 @@ "metadata": {}, "outputs": [], "source": [ - "dsms.search(query=\"Specimen-1\", ktypes=[dsms.ktypes.Dataset])" + "dsms.search(query=\"Specimen-1\", ktypes=[dsms.ktypes.Dataset], limit=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "... and for all of type `Organization` with the annotation `www.example.org/foo`:" + "... and for all of type `Organization` with the annotation `www.researchBACiri.org/foo`:" ] }, { @@ -212,7 +191,7 @@ "outputs": [], "source": [ "dsms.search(\n", - " ktypes=[dsms.ktypes.Organization], annotations=[\"www.fraunhoferBACiri.org/foo\"]\n", + " ktypes=[dsms.ktypes.Organization], annotations=[\"www.researchBACiri.org/foo\"], limit=1\n", " )" ] }, diff --git a/docs/source/tutorials/6_app_HDF5.ipynb b/docs/source/tutorials/6_app_HDF5.ipynb index 44af455..589afa0 100644 --- a/docs/source/tutorials/6_app_HDF5.ipynb +++ b/docs/source/tutorials/6_app_HDF5.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 6. App availability and HDF5\n", + "# Tutorial 6: App availability and HDF5\n", "\n", "In this tutorial we see how to search existing Kitems" ] @@ -15,7 +15,7 @@ "source": [ "### 6.1: Setting up\n", "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] @@ -45,22 +45,6 @@ "dsms = DSMS(env=\"../.env\")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now display what kitems we have" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -159,7 +143,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/environment_hiwi.txt b/environment_hiwi.txt deleted file mode 100644 index 26b5aaf..0000000 --- a/environment_hiwi.txt +++ /dev/null @@ -1,173 +0,0 @@ -# This file may be used to create an environment using: -# $ conda create --name --file -# platform: win-64 -@EXPLICIT -https://repo.anaconda.com/pkgs/main/win-64/blas-1.0-mkl.conda -https://repo.anaconda.com/pkgs/main/win-64/ca-certificates-2024.3.11-haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/icc_rt-2022.1.0-h6049295_2.conda -https://repo.anaconda.com/pkgs/main/win-64/vs2015_runtime-14.27.29016-h5e58377_2.conda -https://repo.anaconda.com/pkgs/main/win-64/winpty-0.4.3-4.conda -https://repo.anaconda.com/pkgs/main/win-64/vc-14.2-h21ff451_1.conda -https://repo.anaconda.com/pkgs/main/win-64/icu-73.1-h6c2663c_0.conda -https://repo.anaconda.com/pkgs/main/win-64/intel-openmp-2023.1.0-h59b6b97_46320.conda -https://repo.anaconda.com/pkgs/main/win-64/jpeg-9e-h2bbff1b_1.conda -https://repo.anaconda.com/pkgs/main/win-64/lerc-3.0-hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/libbrotlicommon-1.0.9-h2bbff1b_7.conda -https://repo.anaconda.com/pkgs/main/win-64/libdeflate-1.17-h2bbff1b_1.conda -https://repo.anaconda.com/pkgs/main/win-64/libffi-3.4.4-hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/libsodium-1.0.18-h62dcd97_0.conda -https://repo.anaconda.com/pkgs/main/win-64/libwebp-base-1.3.2-h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/lz4-c-1.9.4-h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/openssl-1.1.1w-h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/sqlite-3.41.2-h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/tbb-2021.8.0-h59b6b97_0.conda -https://repo.anaconda.com/pkgs/main/win-64/xz-5.4.6-h8cc25b3_0.conda -https://repo.anaconda.com/pkgs/main/win-64/yaml-0.2.5-he774522_0.conda -https://repo.anaconda.com/pkgs/main/win-64/zlib-1.2.13-h8cc25b3_0.conda -https://repo.anaconda.com/pkgs/main/win-64/krb5-1.20.1-h5b6d351_1.conda -https://repo.anaconda.com/pkgs/main/win-64/libbrotlidec-1.0.9-h2bbff1b_7.conda -https://repo.anaconda.com/pkgs/main/win-64/libbrotlienc-1.0.9-h2bbff1b_7.conda -https://repo.anaconda.com/pkgs/main/win-64/libclang13-14.0.6-default_h8e68704_1.conda -https://repo.anaconda.com/pkgs/main/win-64/libpng-1.6.39-h8cc25b3_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl-2023.1.0-h6b88ed4_46358.conda -https://repo.anaconda.com/pkgs/main/win-64/python-3.8.18-h6244533_0.conda -https://repo.anaconda.com/pkgs/main/win-64/zeromq-4.3.5-hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/zstd-1.5.5-hd43e919_0.conda -https://repo.anaconda.com/pkgs/main/win-64/attrs-23.1.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/backcall-0.2.0-pyhd3eb1b0_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/brotli-bin-1.0.9-h2bbff1b_7.conda -https://repo.anaconda.com/pkgs/main/win-64/brotli-python-1.0.9-py38hd77b12b_7.conda -https://repo.anaconda.com/pkgs/main/win-64/certifi-2024.2.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/colorama-0.4.6-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/cycler-0.11.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/debugpy-1.6.7-py38hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/decorator-5.1.1-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/defusedxml-0.7.1-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/exceptiongroup-1.2.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/executing-0.8.3-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/freetype-2.12.1-ha860e81_0.conda -https://repo.anaconda.com/pkgs/main/win-64/idna-3.4-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/json5-0.9.6-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyterlab_widgets-3.0.10-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/kiwisolver-1.4.4-py38hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/libclang-14.0.6-default_hb5a9fac_1.conda -https://repo.anaconda.com/pkgs/main/win-64/libpq-12.15-hb652d5d_1.conda -https://repo.anaconda.com/pkgs/main/win-64/libtiff-4.5.1-hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/markupsafe-2.1.3-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mistune-2.0.4-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl-service-2.4.0-py38h2bbff1b_1.conda -https://repo.anaconda.com/pkgs/main/noarch/munkres-1.1.4-py_0.conda -https://repo.anaconda.com/pkgs/main/win-64/nest-asyncio-1.6.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/overrides-7.4.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/packaging-23.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/pandocfilters-1.5.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/parso-0.8.3-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/pickleshare-0.7.5-pyhd3eb1b0_1003.conda -https://repo.anaconda.com/pkgs/main/win-64/pkgutil-resolve-name-1.3.10-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/platformdirs-3.10.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/ply-3.11-py38_0.conda -https://repo.anaconda.com/pkgs/main/win-64/prometheus_client-0.14.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/psutil-5.9.0-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/pure_eval-0.2.2-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/pycparser-2.21-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pygments-2.15.1-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/pyparsing-3.0.9-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pyqt5-sip-12.13.0-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/python-fastjsonschema-2.16.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/python-json-logger-2.0.7-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/python-tzdata-2023.3-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pytz-2023.3.post1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pywin32-305-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pywinpty-2.0.10-py38h5da7b33_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pyyaml-6.0.1-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pyzmq-25.1.2-py38hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/rfc3986-validator-0.1.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/rpds-py-0.10.6-py38h062c2fa_0.conda -https://repo.anaconda.com/pkgs/main/win-64/send2trash-1.8.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/setuptools-68.2.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/six-1.16.0-pyhd3eb1b0_1.conda -https://repo.anaconda.com/pkgs/main/win-64/sniffio-1.3.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/soupsieve-2.5-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/tomli-2.0.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/tornado-6.3.3-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/traitlets-5.7.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/typing_extensions-4.9.0-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/noarch/wcwidth-0.2.5-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/webencodings-0.5.1-py38_1.conda -https://repo.anaconda.com/pkgs/main/win-64/wheel-0.41.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/widgetsnbextension-4.0.10-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/win_inet_pton-1.1.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/zipp-3.17.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/anyio-4.2.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/asttokens-2.0.5-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/async-lru-2.0.4-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/babel-2.11.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/beautifulsoup4-4.12.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/bleach-4.1.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/brotli-1.0.9-h2bbff1b_7.conda -https://repo.anaconda.com/pkgs/main/win-64/cffi-1.16.0-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/comm-0.2.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/importlib-metadata-7.0.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/importlib_resources-6.1.1-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/jedi-0.18.1-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/jinja2-3.1.3-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_core-5.5.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/jupyterlab_pygments-0.1.2-py_0.conda -https://repo.anaconda.com/pkgs/main/win-64/matplotlib-inline-0.1.6-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/numpy-base-1.24.3-py38h8a87ada_1.conda -https://repo.anaconda.com/pkgs/main/win-64/openjpeg-2.4.0-h4fc8c34_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pip-23.3.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/prompt-toolkit-3.0.43-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pysocks-1.7.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/noarch/python-dateutil-2.8.2-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/qt-main-5.15.2-he2df7e6_10.conda -https://repo.anaconda.com/pkgs/main/win-64/qtpy-2.4.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/referencing-0.30.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/rfc3339-validator-0.1.4-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/sip-6.7.12-py38hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/terminado-0.17.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/tinycss2-1.2.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/typing-extensions-4.9.0-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/websocket-client-0.58.0-py38haa95532_4.conda -https://repo.anaconda.com/pkgs/main/win-64/argon2-cffi-bindings-21.2.0-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/fonttools-4.25.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/noarch/importlib_metadata-7.0.1-hd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jsonschema-specifications-2023.7.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_server_terminals-0.4.4-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/pillow-10.2.0-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/prompt_toolkit-3.0.43-hd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pyqt-5.15.10-py38hd77b12b_0.conda -https://repo.anaconda.com/pkgs/main/noarch/stack_data-0.2.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/urllib3-2.1.0-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/noarch/argon2-cffi-21.3.0-pyhd3eb1b0_0.conda -https://repo.anaconda.com/pkgs/main/win-64/ipython-8.12.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jsonschema-4.19.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_client-8.6.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/requests-2.31.0-py38haa95532_1.conda -https://repo.anaconda.com/pkgs/main/win-64/ipykernel-6.28.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/ipywidgets-8.1.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_events-0.8.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/nbformat-5.9.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pooch-1.7.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_console-6.6.3-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/nbclient-0.8.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/qtconsole-5.5.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/nbconvert-7.10.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter_server-2.10.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter-lsp-2.2.0-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyterlab_server-2.25.1-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/notebook-shim-0.2.3-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyterlab-4.0.11-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/notebook-7.0.8-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jupyter-1.0.0-py38haa95532_9.conda -https://repo.anaconda.com/pkgs/main/win-64/bottleneck-1.3.7-py38h9128911_0.conda -https://repo.anaconda.com/pkgs/main/win-64/contourpy-1.0.5-py38h59b6b97_0.conda -https://repo.anaconda.com/pkgs/main/win-64/matplotlib-3.7.2-py38haa95532_0.conda -https://repo.anaconda.com/pkgs/main/win-64/matplotlib-base-3.7.2-py38h4ed8f06_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl_fft-1.3.8-py38h2bbff1b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mkl_random-1.2.4-py38h59b6b97_0.conda -https://repo.anaconda.com/pkgs/main/win-64/numpy-1.24.3-py38h79a8e48_1.conda -https://repo.anaconda.com/pkgs/main/win-64/numexpr-2.8.4-py38h7b80656_1.conda -https://repo.anaconda.com/pkgs/main/win-64/scipy-1.10.1-py38hdcfc7df_1.conda -https://repo.anaconda.com/pkgs/main/win-64/pandas-2.0.3-py38h4ed8f06_0.conda diff --git a/examples/Chapters/1_introduction.ipynb b/examples/Chapters/1_introduction.ipynb deleted file mode 100644 index ed9bacd..0000000 --- a/examples/Chapters/1_introduction.ipynb +++ /dev/null @@ -1,139 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 1. Introduction to the DSMS-SDK\n", - "\n", - "In this tutorial we see the overview on how to setup and basic use DSMS-SDK\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.1. Setting up\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.2. Introduction to KItems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see which kind of DSMS-object we own as a user:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can investigate what a KItem needs in order to be created. KItems are entirely based on [`Pydantic`](https://docs.pydantic.dev/latest/)-Models (v2), hence the properties (in `Pydantic` called `Fields`) are automatically validated once we set them. \n", - "\n", - "The schema of the KItem itself is a JSON schema which is machine-readable and can be directly incorporated into [Swagger](https://swagger.io/tools/swagger-ui/)-supported APIs like e.g. [`FastAPI`](https://fastapi.tiangolo.com/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pprint(KItem.model_json_schema())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can investigate the KTypes defined in the remote instance:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for ktype in dsms.ktypes:\n", - " print(ktype)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/Chapters/2_creation.ipynb b/examples/Chapters/2_creation.ipynb deleted file mode 100644 index ff25891..0000000 --- a/examples/Chapters/2_creation.ipynb +++ /dev/null @@ -1,168 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 2. Create Kitems\n", - "\n", - "In this tutorial we see how to create new Kitems." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.1: Setting up\n", - "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.2: Create KItems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can make new KItems by simple class-initiation: (Make Sure existing KItems is not put as input). \n", - "#" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "item = KItem(\n", - " name=\"foo123\",\n", - " ktype_id=dsms.ktypes.DatasetCatalog,\n", - " custom_properties={\"foo\": \"bar\"},\n", - ")\n", - "\n", - "item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Remember: changes are only syncronized with the DSMS when you call the `commit`-method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the object we created before running the `commit`-method has automatically been updated, e.g. with the creation- and update-timestamp:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And the list of our KItems in the DSMS has been updated as well:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/Chapters/3_updation.ipynb b/examples/Chapters/3_updation.ipynb deleted file mode 100644 index e5e5339..0000000 --- a/examples/Chapters/3_updation.ipynb +++ /dev/null @@ -1,171 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 3. Updating KItems\n", - "\n", - "In this tutorial we see how to update existing Kitems." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.1. Setting up\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.2. Updating Kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we would like to update the properties of our KItem we created previously.\n", - "\n", - "Depending on the schema of each property (see `KItem.model_schema_json()` in the **Introduction** of this tutorial), we can simply use the standard `list`-method as we know them from basic Python (e.g. for the `annotations`, `attachments`, `external_link`, etc). \n", - "\n", - "\n", - "Other properties which are not `list`-like can be simply set by attribute-assignment (e.g. `name`, `slug`, `ktype_id`, etc)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# specify the path to any arbitrary file to be uploaded\n", - "file = os.path.join(\"..\", \"README.md\")\n", - "\n", - "item.name = \"foobar\"\n", - "item.custom_properties.update({\"foobar\": \"foobar\"})\n", - "item.attachments.append({\"name\": file})\n", - "item.annotations.append(\n", - " {\n", - " \"iri\": \"www.example.org/foo\",\n", - " \"name\": \"example class\",\n", - " \"namespace\": \"www.example.org\",\n", - " }\n", - ")\n", - "item.external_links.append(\n", - " {\"url\": \"http://example.org\", \"label\": \"example link\"}\n", - ")\n", - "item.contacts.append({\"name\": \"foo\", \"email\": \"foo@bar.mail\"})\n", - "item.affiliations.append({\"name\": \"foobar team\"})\n", - "item.user_groups.append({\"name\": \"foogroup\", \"group_id\": \"123\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Changes are sent to the DSMS through the `commit`-method again." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see now that e.g. the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have beem thrown during the `commit`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Furthermore we can also download the file we uploaded again:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for file in item.attachments:\n", - " download = file.download()\n", - "\n", - " print(\"\\t\\t\\t Downloaded file:\", file)\n", - " print(\"|------------------------------------Beginning of file------------------------------------|\")\n", - " print(download)\n", - " print(\"|---------------------------------------End of file---------------------------------------|\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/Chapters/4_deletion.ipynb b/examples/Chapters/4_deletion.ipynb deleted file mode 100644 index 1edf808..0000000 --- a/examples/Chapters/4_deletion.ipynb +++ /dev/null @@ -1,219 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 4. Deleting KItems\n", - "\n", - "In this tutorial we see how to delete new Kitems and their properties." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 4.1. Setting up\n", - "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 4.2. Deletion of KItems and their properties" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also remove properties from the KItem without deleting the KItem itself.\n", - "\n", - "For the `list`-like properties, we can use the standard `list`-methods from basic Python again (e.g. `pop`, `remove`, etc. or the `del`-operator).\n", - "\n", - "For the other, non-`list`-like properties, we can simply use the attribute-assignment again.\n", - "\n", - "When we only want single parts of the properties in the KItem, we can do it like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "item.attachments.pop(0)\n", - "item.annotations.pop(0)\n", - "item.external_links.pop(0)\n", - "item.contacts.pop(0)\n", - "item.user_groups.pop(0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, we can also reset the entire property by setting it to e.g. an empty list again:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "item.affiliations = []" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "See the changes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "item" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Send the changes to the DSMS with the `commit`-method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, we can also delete the whole KItem from the DSMS by applying the `del`-operator to the `dsms`-object with the individual `KItem`-object:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "del dsms[item]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Commit the changes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see know, our KItem has been removed from our inventory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/Chapters/5_search.ipynb b/examples/Chapters/5_search.ipynb deleted file mode 100644 index b8527a8..0000000 --- a/examples/Chapters/5_search.ipynb +++ /dev/null @@ -1,248 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 5. Searching Kitems\n", - "\n", - "In this tutorial we see how to search existing Kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.1. Setting up\n", - "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see which kind of DSMS-object we own as a user:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.2. Searching for KItems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the last unit of this tutorial, we would like to search for specfic KItems we created in the DSMS.\n", - "\n", - "For this purpose, we will firstly create some KItems and apply the `search`-method on the `DSMS`-object later on in order to find them again in the DSMS.\n", - "\n", - "We also wnat to demonstrate here, that we can link KItems to each other in order to find e.g. a related item of type `DatasetCatalog`. For this strategy, we are using the `linked_kitems`-attribute and the `id` of the item which we would like to link.\n", - "\n", - "The procedure looks like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "item = KItem(\n", - " name=\"foo 1\",\n", - " ktype_id=dsms.ktypes.DatasetCatalog\n", - ")\n", - "\n", - "item2 = KItem(\n", - " name=\"foo 2\",\n", - " ktype_id=dsms.ktypes.Organization,\n", - " linked_kitems=[item],\n", - " annotations=[\n", - " {\n", - " \"iri\": \"www.example.org/foo\",\n", - " \"name\": \"foo\",\n", - " \"namespace\": \"www.example.org\",\n", - " }\n", - " ],\n", - ")\n", - "item3 = KItem(\n", - " name=\"foo 3\", \n", - " ktype_id=dsms.ktypes.Organization\n", - ")\n", - "item4 = KItem(\n", - " name=\"foo 4\",\n", - " ktype_id=dsms.ktypes.Organization,\n", - " annotations=[\n", - " {\n", - " \"iri\": \"www.example.org/bar\",\n", - " \"name\": \"bar\",\n", - " \"namespace\": \"https://www.example.org\",\n", - " }\n", - " ],\n", - ")\n", - "\n", - "dsms.commit()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we are apply to search for e.g. kitems of type `DatasetCatalog`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... and for all of type `Organization` and `DatasetCatalog`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... or for all of type `DatasetCatalog` with `foo` in the name:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.search(query=\"foo\", ktypes=[dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... and for all of type `Organization` with the annotation `www.example.org/foo`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.search(\n", - " ktypes=[dsms.ktypes.Organization], annotations=[\"www.example.org/foo\"]\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Clean up the DSMS from the tutortial:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "del dsms[item]\n", - "del dsms[item2]\n", - "del dsms[item3]\n", - "del dsms[item4]\n", - "\n", - "dsms.commit()\n", - "\n", - "dsms.kitems" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/Chapters/6_app_HDF5.ipynb b/examples/Chapters/6_app_HDF5.ipynb deleted file mode 100644 index 9f9368a..0000000 --- a/examples/Chapters/6_app_HDF5.ipynb +++ /dev/null @@ -1,182 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 6. Available apps investigation and HDF5\n", - "\n", - "In this tutorial we see how to :\n", - "\n", - "1.Investigate which apps are available \n", - "\n", - "2.Extract kitems into dataframes via retrieval using hdf5 format." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 6.1: Setting up\n", - "\n", - "Before you run this tutorial: make sure to have access to an DSMS-instance of your interest, that you have installed this package and that you have copied the needed variables such as the `DSMS_HOST_URL` and `DSMS_TOKEN` into an `.env`-file.\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pprint import pprint\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#specify path to an arbitrary file\n", - "env = os.path.join(\"..\", \".env\")\n", - "\n", - "# start the session\n", - "load_dotenv(env)\n", - "\n", - "dsms = DSMS()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see which kind of DSMS-object we own as a user:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 6.1. Investigating Available Apps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can investigate which apps are available through JupyterLab:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.apps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 6.2. Extraction into Data Frame for further analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are also able to upload dataframes or time series data and investigate them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", - "\n", - "\n", - "item = KItem(name=\"testdata123\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", - "dsms.commit()\n", - "\n", - "print(\"Column-wise:\")\n", - "for column in item.hdf5:\n", - " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n", - "\n", - "df = item.hdf5.to_df()\n", - "print(\"\\nAs data frame:\")\n", - "print(df)\n", - "\n", - "new_df = df.drop(['a'], axis=1)\n", - "item.hdf5 = new_df\n", - "\n", - "dsms.commit()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "del dsms[item]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 5c8cbdc..30b935f 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -1257,7 +1257,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.10.13" } }, "nbformat": 4, From 7677ace9ecf00e64439bb09a617ad730e6143b54 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 27 Jun 2024 15:24:58 +0000 Subject: [PATCH 086/114] Change of relative path while DSMS intialisation in tutorials --- docs/source/index.md | 2 +- docs/source/tutorials/1_introduction.ipynb | 86 +-------- docs/source/tutorials/2_creation.ipynb | 201 ++------------------- docs/source/tutorials/3_updation.ipynb | 86 +-------- docs/source/tutorials/4_deletion.ipynb | 2 +- docs/source/tutorials/5_search.ipynb | 2 +- docs/source/tutorials/6_app_HDF5.ipynb | 2 +- 7 files changed, 34 insertions(+), 347 deletions(-) diff --git a/docs/source/index.md b/docs/source/index.md index 5db57f9..f3d0975 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -2,7 +2,7 @@ Welcome to documentation of DSMS! -Here you will find all the information regarding the installation, setup and basic usage of the DSMS and the python based DSMS-SDK. +Here you will find all the information about DSMS and installation, setup and basic usage of the associated python based DSMS-SDK. ````{panels} diff --git a/docs/source/tutorials/1_introduction.ipynb b/docs/source/tutorials/1_introduction.ipynb index e3ea410..32895d0 100644 --- a/docs/source/tutorials/1_introduction.ipynb +++ b/docs/source/tutorials/1_introduction.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -38,11 +38,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(env=\"../../../.env\")" ] }, { @@ -61,67 +61,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[KItem(\n", - " \n", - " \tname = machine 1, \n", - " \n", - " \tid = 2e235212-733d-44e5-8bda-38f13be6475e, \n", - " \n", - " \tktype_id = testingmachine, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = machine-1, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 9c8a295a-aeed-46c1-87f1-2793ef78320b\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-04-30 12:58:54.233036, \n", - " \n", - " \tupdated_at = 2024-04-30 12:58:54.233036, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " )]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.kitems" ] @@ -139,23 +81,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "KTypes.Organization\n", - "KTypes.Expert\n", - "KTypes.App\n", - "KTypes.DatasetCatalog\n", - "KTypes.Dataset\n", - "KTypes.Specimen\n", - "KTypes.Testingmachine\n" - ] - } - ], + "outputs": [], "source": [ "for ktype in dsms.ktypes:\n", " print(ktype)" diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/source/tutorials/2_creation.ipynb index 52ada9d..75924a2 100644 --- a/docs/source/tutorials/2_creation.ipynb +++ b/docs/source/tutorials/2_creation.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -37,11 +37,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(env=\"../../../.env\")" ] }, { @@ -62,75 +62,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "KItem(\n", - "\n", - "\tname = Machine-1, \n", - "\n", - "\tid = d377511c-8086-466a-9972-2e4e002f8b18, \n", - "\n", - "\tktype_id = KTypes.TestingMachine, \n", - "\n", - "\tin_backend = False, \n", - "\n", - "\tslug = machine-1-d377511c, \n", - "\n", - "\tannotations = [], \n", - "\n", - "\tattachments = [], \n", - "\n", - "\tlinked_kitems = [], \n", - "\n", - "\taffiliations = [], \n", - "\n", - "\tauthors = [], \n", - "\n", - "\tavatar_exists = False, \n", - "\n", - "\tcontacts = [], \n", - "\n", - "\tcreated_at = None, \n", - "\n", - "\tupdated_at = None, \n", - "\n", - "\texternal_links = [], \n", - "\n", - "\tkitem_apps = [], \n", - "\n", - "\tsummary = None, \n", - "\n", - "\tuser_groups = [], \n", - "\n", - "\tcustom_properties = {\n", - "\t\tsupplier: None, \n", - "\t\tmaximumforce: None, \n", - "\t\tmaximumspeed: None, \n", - "\t\tlocation: None, \n", - "\t\tcalibrationcomponents: None, \n", - "\t\tcalibrationstatus: None, \n", - "\t\tinventorynumber: None, \n", - "\t\tserialnumber: None, \n", - "\t\tProducer: TestingLab GmBH, \n", - "\t\tRoom Number: A404, \n", - "\t\tDescription: Bending Test Machine\n", - "\t}, \n", - "\n", - "\thdf5 = None, \n", - "\n", - "\trdf_exists = False\n", - ")" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item = KItem(\n", " name=\"Machine-1\",\n", @@ -153,20 +87,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'https://stahldigital.materials-data.space/knowledge/testing-machine/machine-1-d377511c'" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "dsms.commit()\n", "item.url" @@ -181,83 +104,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "KItem(\n", - "\n", - "\tname = Machine-1, \n", - "\n", - "\tid = d377511c-8086-466a-9972-2e4e002f8b18, \n", - "\n", - "\tktype_id = testing-machine, \n", - "\n", - "\tin_backend = True, \n", - "\n", - "\tslug = machine-1-d377511c, \n", - "\n", - "\tannotations = [], \n", - "\n", - "\tattachments = [\n", - "\t\t{\n", - "\t\t\tname: subgraph.ttl\n", - "\t\t}\n", - "\t], \n", - "\n", - "\tlinked_kitems = [], \n", - "\n", - "\taffiliations = [], \n", - "\n", - "\tauthors = [\n", - "\t\t{\n", - "\t\t\tuser_id: 9c8a295a-aeed-46c1-87f1-2793ef78320b\n", - "\t\t}\n", - "\t], \n", - "\n", - "\tavatar_exists = False, \n", - "\n", - "\tcontacts = [], \n", - "\n", - "\tcreated_at = 2024-06-27 14:01:23.084258, \n", - "\n", - "\tupdated_at = 2024-06-27 14:01:23.084258, \n", - "\n", - "\texternal_links = [], \n", - "\n", - "\tkitem_apps = [], \n", - "\n", - "\tsummary = None, \n", - "\n", - "\tuser_groups = [], \n", - "\n", - "\tcustom_properties = {\n", - "\t\tsupplier: None, \n", - "\t\tmaximumforce: None, \n", - "\t\tmaximumspeed: None, \n", - "\t\tlocation: None, \n", - "\t\tcalibrationcomponents: None, \n", - "\t\tcalibrationstatus: None, \n", - "\t\tinventorynumber: None, \n", - "\t\tserialnumber: None, \n", - "\t\tProducer: TestingLab GmBH, \n", - "\t\tRoom Number: A404, \n", - "\t\tDescription: Bending Test Machine\n", - "\t}, \n", - "\n", - "\thdf5 = None, \n", - "\n", - "\trdf_exists = True\n", - ")" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item" ] @@ -271,20 +120,9 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Machine-1'" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "item.name" ] @@ -293,25 +131,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To check the type of the item newly created, can be done as follows:" + "To check the type of the item newly created we can use the following:" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dsms.knowledge.kitem.KItem" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "type(item)" ] diff --git a/docs/source/tutorials/3_updation.ipynb b/docs/source/tutorials/3_updation.ipynb index 8f9e6aa..1e65563 100644 --- a/docs/source/tutorials/3_updation.ipynb +++ b/docs/source/tutorials/3_updation.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -38,11 +38,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(env=\"../../../.env\")" ] }, { @@ -54,17 +54,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Machine-1\n" - ] - } - ], + "outputs": [], "source": [ "item = dsms.kitems[0]\n", "print(item.name)" @@ -90,7 +82,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -108,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -131,69 +123,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\t\t\t Downloaded file: {\n", - "\t\t\tname: README.md\n", - "\t\t}\n", - "|------------------------------------Beginning of file------------------------------------|\n", - "# DSMS-SDK\n", - "Python SDK core-package for interacting with the Dataspace Management System (DSMS)\n", - "\n", - "\n", - "## Authors\n", - "\n", - "[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM)\n", - "\n", - "## License\n", - "\n", - "This project is licensed under the BSD 3-Clause. See the LICENSE file for more information.\n", - "\n", - "## Usage\n", - "\n", - "The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities:\n", - "\n", - "- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType)\n", - " - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test\n", - " - Administrating authorship, contact information and supplementary information upon making changes or adding KItems\n", - " - Semantic annotation of KItems\n", - "- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface\n", - "- Linking KItems to other KItems\n", - "- Linking Apps to KItems, triggererd, for example, during a file upload\n", - "- Performing simple file upload and download using attachments to KItems\n", - "- Export of a knowledge (sub) graph as common serializations (.ttl, .json)\n", - "\n", - "For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items.\n", - "\n", - "\n", - "## Disclaimer\n", - "\n", - "Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM.\n", - "\n", - "Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de)\n", - "\n", - "|---------------------------------------End of file---------------------------------------|\n", - "\t\t\t Downloaded file: {\n", - "\t\t\tname: subgraph.ttl\n", - "\t\t}\n", - "|------------------------------------Beginning of file------------------------------------|\n", - "\n", - " a .\n", - "\n", - "\n", - "|---------------------------------------End of file---------------------------------------|\n" - ] - } - ], + "outputs": [], "source": [ "for file in item.attachments:\n", " download = file.download()\n", diff --git a/docs/source/tutorials/4_deletion.ipynb b/docs/source/tutorials/4_deletion.ipynb index 16ff2e3..603091c 100644 --- a/docs/source/tutorials/4_deletion.ipynb +++ b/docs/source/tutorials/4_deletion.ipynb @@ -42,7 +42,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(env=\"../../../.env\")" ] }, { diff --git a/docs/source/tutorials/5_search.ipynb b/docs/source/tutorials/5_search.ipynb index 3268951..047dc18 100644 --- a/docs/source/tutorials/5_search.ipynb +++ b/docs/source/tutorials/5_search.ipynb @@ -42,7 +42,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(env=\"../../../.env\")" ] }, { diff --git a/docs/source/tutorials/6_app_HDF5.ipynb b/docs/source/tutorials/6_app_HDF5.ipynb index 589afa0..b20c513 100644 --- a/docs/source/tutorials/6_app_HDF5.ipynb +++ b/docs/source/tutorials/6_app_HDF5.ipynb @@ -42,7 +42,7 @@ "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../.env\")" + "dsms = DSMS(env=\"../../../.env\")" ] }, { From e4fe554297b60882020b0e7be35dd2e880daf6c5 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 27 Jun 2024 16:22:01 +0000 Subject: [PATCH 087/114] merged with v1.4.0rc19 --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cbc003c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ + +*.ipynb diff=jupyternotebook + +*.ipynb merge=jupyternotebook From 7c1b81d4c75ecd09326156f1bc6e145ba171a4d0 Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 27 Jun 2024 16:35:11 +0000 Subject: [PATCH 088/114] fixing reminder tip formatting in dsms_config_schema.md --- docs/source/dsms_config_schema.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/source/dsms_config_schema.md b/docs/source/dsms_config_schema.md index fcaee7d..c0675de 100644 --- a/docs/source/dsms_config_schema.md +++ b/docs/source/dsms_config_schema.md @@ -49,12 +49,5 @@ print(config) ``` -> **Reminder Tip** -> -> As discussed in previous section in DSMS SDK section (refer to the [Accessing DSMS Core](dsms_sdk.md#accessing-dsms-core)) for accessing DSMS core, you either need to pass during initialization: -> -> `username` and `password` -> -> OR -> -> `token` +> **Reminder Tip :** +> As discussed in previous section in DSMS SDK section (refer to the [Accessing DSMS Core](dsms_sdk.md#accessing-dsms-core)) for accessing DSMS core, you either need to pass during initialization : `token` or `username + password`. From 3f47987fa328903a18c98f7780c7ca304c82f67b Mon Sep 17 00:00:00 2001 From: Priyabrat Mishra Date: Thu, 27 Jun 2024 16:37:49 +0000 Subject: [PATCH 089/114] Removing extra README.md in docs/source --- docs/source/README.md | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 docs/source/README.md diff --git a/docs/source/README.md b/docs/source/README.md deleted file mode 100644 index 7703aba..0000000 --- a/docs/source/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# DSMS-SDK -Python SDK core-package for interacting with the Dataspace Management System (DSMS) - - -## Authors - -[Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) - -[Yoav Nahshon](mailto:yoav.nahshon@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) - -[Pablo De Andres](mailto:pablo.de.andres@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) - -## License - -This project is licensed under the BSD 3-Clause. See the LICENSE file for more information. - -## Usage - -The SDK provides a general Python interface to a remote DSMS deployment, allowing users to access, store and link data in a DSMS instance easily and safely. The package provides the following main capabilities: - -- Managing Knowledge-Items (KItems), which are data instances of an explicitly defined semantic class type (KType) - - Creating, updating and deleting meta data and properties, e.g. date, operator, material response data for a conducted tensile test - - Administrating authorship, contact information and supplementary information upon making changes or adding KItems - - Semantic annotation of KItems -- Conduct simple free-text searches within the DSMS instance including filters (e.g. limiting the search for certain materials) as well as a more experts-aware SPARQL interface -- Linking KItems to other KItems -- Linking Apps to KItems, triggererd, for example, during a file upload -- Performing simple file upload and download using attachments to KItems -- Export of a knowledge (sub) graph as common serializations (.ttl, .json) - -For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items. - - -## Disclaimer - -Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM. - -Contact: [Matthias Büschelberger](mailto:matthias.bueschelberger@iwm.fraunhofer.de) From afdecef622ef39841d8f77cba41ac1b3d1565ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 1 Aug 2024 15:37:24 +0200 Subject: [PATCH 090/114] update schema for attachment comparison during commit --- dsms/knowledge/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 193aeaa..e177b67 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -428,16 +428,21 @@ def _get_linked_diffs( def _get_attachment_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): """Check which attachments should be removed and which should be added.""" + old_attachments = [ + attachment.get("name") + for attachment in kitem_old.get("attachments") + if attachment.get("name") + ] return { "remove": [ attachment - for attachment in kitem_old.get("attachments") + for attachment in old_attachments if attachment not in kitem_new.attachments.by_name ], "add": [ attachment.name for name, attachment in kitem_new.attachments.by_name.items() - if name not in kitem_old.get("attachments") + if name not in old_attachments ], } From dbc7b1ebc382f09b36fb46472f17d0e139bd40b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 1 Aug 2024 15:37:57 +0200 Subject: [PATCH 091/114] Bump version v1.4.0rc19 -> v1.4.0rc20 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9a93351..c4f4399 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc19 +version = v1.4.0rc20 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From fb3219dce9f044335ae1868b9248866df89fef11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 1 Aug 2024 17:25:56 +0200 Subject: [PATCH 092/114] rename hdf5 to dataframe --- dsms/core/configuration.py | 4 +- dsms/knowledge/kitem.py | 34 +- dsms/knowledge/properties/__init__.py | 4 +- .../properties/custom_datatype/numerical.py | 2 +- .../properties/{hdf5.py => dataframe.py} | 18 +- dsms/knowledge/semantics/units/base.py | 6 +- dsms/knowledge/semantics/units/sparql.py | 2 +- dsms/knowledge/semantics/units/utils.py | 6 +- dsms/knowledge/utils.py | 40 +- examples/basic_usage.ipynb | 3794 ++--------------- examples/unit_conversion.ipynb | 6 +- tests/conftest.py | 14 +- 12 files changed, 378 insertions(+), 3552 deletions(-) rename dsms/knowledge/properties/{hdf5.py => dataframe.py} (89%) diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index edb6382..cc753b9 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -80,7 +80,7 @@ class Configuration(BaseSettings): display_units: bool = Field( False, - description="""Whether the custom properties or the hdf5 columns shall + description="""Whether the custom properties or the dataframe columns shall directly reveal their unit when printed. WARNING: This might lead to performance issues.""", ) @@ -112,7 +112,7 @@ class Configuration(BaseSettings): pattern=MODULE_REGEX, description="""Class and Module specification in Python for a subclass of `dsms.knowledge.semantics.units.base:BaseUnitSparqlQuery` in order to retrieve - the units of a HDF5 column/ custom property of a KItem.""", + the units of a DataFrame column/ custom property of a KItem.""", ) hide_properties: Set[Union[str, None]] = Field( diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 4afd9b6..f5c702d 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -39,7 +39,7 @@ ExternalLink, ExternalLinksProperty, KItemPropertyList, - HDF5Container, + DataFrameContainer, Column, LinkedKItem, LinkedKItemsProperty, @@ -55,7 +55,7 @@ _get_kitem, _slug_is_available, _slugify, - _inspect_hdf5, + _inspect_dataframe, _make_annotation_schema, ) @@ -110,8 +110,8 @@ class KItem(BaseModel): User groups able to access the KItem. custom_properties (Optional[Any]): Custom properties associated with the KItem. - hdf5 (Optional[Union[List[Column], pd.DataFrame, Dict[str, Union[List, Dict]]]]): - HDF5 interface. + dataframe (Optional[Union[List[Column], pd.DataFrame, Dict[str, Union[List, Dict]]]]): + DataFrame interface. """ # public @@ -178,9 +178,9 @@ class KItem(BaseModel): None, description="KType of the KItem", exclude=True ) - hdf5: Optional[ + dataframe: Optional[ Union[List[Column], pd.DataFrame, Dict[str, Union[List, Dict]]] - ] = Field(None, description="HDF5 interface.") + ] = Field(None, description="DataFrame interface.") rdf_exists: bool = Field( False, description="Whether the KItem holds an RDF Graph or not." @@ -504,33 +504,35 @@ def validate_avatar(cls, value: "Union[Dict, Avatar]") -> Avatar: value = Avatar(kitem=cls, **value) return value - @field_validator("hdf5") + @field_validator("dataframe") @classmethod - def validate_hdf5( + def validate_dataframe( cls, value: Union[ List[Column], pd.DataFrame, Dict[str, Dict[Any, Any]] ], # pylint: disable=unused-argument info: ValidationInfo, - ) -> HDF5Container: - """Get HDF5 container if it exists.""" + ) -> DataFrameContainer: + """Get DataFrame container if it exists.""" kitem_id = info.data.get("id") if isinstance(value, (pd.DataFrame, dict)): if isinstance(value, pd.DataFrame): - hdf5 = value.copy(deep=True) + dataframe = value.copy(deep=True) elif isinstance(value, dict): - hdf5 = pd.DataFrame.from_dict(value) + dataframe = pd.DataFrame.from_dict(value) else: raise TypeError( f"Data must be of type {dict} or {pd.DataFrame}, not {type(value)}" ) else: - columns = _inspect_hdf5(kitem_id) + columns = _inspect_dataframe(kitem_id) if columns: - hdf5 = HDF5Container([Column(**column) for column in columns]) + dataframe = DataFrameContainer( + [Column(**column) for column in columns] + ) else: - hdf5 = None - return hdf5 + dataframe = None + return dataframe @model_validator(mode="after") @classmethod diff --git a/dsms/knowledge/properties/__init__.py b/dsms/knowledge/properties/__init__.py index 3d5c9b5..13b8547 100644 --- a/dsms/knowledge/properties/__init__.py +++ b/dsms/knowledge/properties/__init__.py @@ -13,7 +13,7 @@ from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.properties.contacts import ContactInfo, ContactsProperty from dsms.knowledge.properties.custom_datatype import NumericalDataType -from dsms.knowledge.properties.hdf5 import Column, HDF5Container +from dsms.knowledge.properties.dataframe import Column, DataFrameContainer from dsms.knowledge.properties.summary import Summary from dsms.knowledge.properties.user_groups import UserGroup, UserGroupsProperty @@ -58,7 +58,7 @@ "Summary", "KItemPropertyList", "KItemProperty", - "HDF5Container", + "DataFrameContainer", "Column", "NumericalDataType", ] diff --git a/dsms/knowledge/properties/custom_datatype/numerical.py b/dsms/knowledge/properties/custom_datatype/numerical.py index c996774..65c6736 100644 --- a/dsms/knowledge/properties/custom_datatype/numerical.py +++ b/dsms/knowledge/properties/custom_datatype/numerical.py @@ -66,7 +66,7 @@ def get_unit(self) -> "Dict[str, Any]": return get_property_unit( self.kitem.id, self.name, - is_hdf5_column=True, + is_dataframe_column=True, autocomplete_symbol=self.kitem.dsms.config.autocomplete_units, ) diff --git a/dsms/knowledge/properties/hdf5.py b/dsms/knowledge/properties/dataframe.py similarity index 89% rename from dsms/knowledge/properties/hdf5.py rename to dsms/knowledge/properties/dataframe.py index b350d59..b994b4a 100644 --- a/dsms/knowledge/properties/hdf5.py +++ b/dsms/knowledge/properties/dataframe.py @@ -1,4 +1,4 @@ -"""HDF5 property of a KItem""" +"""DataFrame property of a KItem""" import logging from typing import TYPE_CHECKING @@ -6,7 +6,7 @@ from pydantic import Field from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList -from dsms.knowledge.utils import _get_hdf5_column, _is_number +from dsms.knowledge.utils import _get_dataframe_column, _is_number from dsms.knowledge.semantics.units import ( # isort:skip get_conversion_factor, @@ -21,7 +21,7 @@ class Column(KItemProperty): """ - Column of an HDF5 data frame. + Column of an DataFrame data frame. Attributes: column_id (int): Column ID in the data frame. @@ -57,7 +57,7 @@ def get(self) -> "List[Any]": Returns: List[Any]: List of data for the column. """ - return _get_hdf5_column(self.id, self.column_id) + return _get_dataframe_column(self.id, self.column_id) def get_unit(self) -> "Dict[str, Any]": """ @@ -69,7 +69,7 @@ def get_unit(self) -> "Dict[str, Any]": return get_property_unit( self.id, self.name, - is_hdf5_column=True, + is_dataframe_column=True, autocomplete_symbol=self.kitem.dsms.config.autocomplete_units, ) @@ -105,8 +105,8 @@ def convert_to( ] -class HDF5Container(KItemPropertyList): - """HDF5 container of a data frame related to a KItem""" +class DataFrameContainer(KItemPropertyList): + """DataFrame container of a data frame related to a KItem""" # OVERRIDE @property @@ -116,10 +116,10 @@ def k_property_item(cls) -> "Callable": # OVERRIDE @property def k_property_helper(cls) -> None: - """Not defined for HDF5""" + """Not defined for DataFrame""" def to_df(self) -> pd.DataFrame: - """Return hdf5 as pandas DataFrame""" + """Return dataframe as pandas DataFrame""" data = {column.name: column.get() for column in self} return pd.DataFrame.from_dict(data) diff --git a/dsms/knowledge/semantics/units/base.py b/dsms/knowledge/semantics/units/base.py index 541d285..cbc76c2 100644 --- a/dsms/knowledge/semantics/units/base.py +++ b/dsms/knowledge/semantics/units/base.py @@ -12,7 +12,7 @@ class BaseUnitSparqlQuery(BaseSparqlQuery): """ Abstract class for defining sparql queries fetching - the units of a hdf5 column or custom property of + the units of a dataframe column or custom property of a kitem. """ @@ -20,13 +20,13 @@ def __init__( self, kitem_id: "Union[str, UUID]", property_name: str, - is_hdf5_column: bool = False, + is_dataframe_column: bool = False, autocomplete_symbol: bool = True, ) -> None: super().__init__( kitem_id=kitem_id, property_name=property_name, - is_hdf5_column=is_hdf5_column, + is_dataframe_column=is_dataframe_column, autocomplete_symbol=autocomplete_symbol, ) diff --git a/dsms/knowledge/semantics/units/sparql.py b/dsms/knowledge/semantics/units/sparql.py index 93ecca8..a35056a 100644 --- a/dsms/knowledge/semantics/units/sparql.py +++ b/dsms/knowledge/semantics/units/sparql.py @@ -19,7 +19,7 @@ class UnitSparqlQuery(BaseUnitSparqlQuery): # OVERRIDE @property def query(cls) -> str: - """Construct sparql query for getting unit for hdf5 column""" + """Construct sparql query for getting unit for dataframe column""" kitem_id = cls.kwargs.get("kitem_id") property_name = cls.kwargs.get("property_name") if not kitem_id: diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py index 8a2de98..6870cca 100644 --- a/dsms/knowledge/semantics/units/utils.py +++ b/dsms/knowledge/semantics/units/utils.py @@ -68,7 +68,7 @@ def get_conversion_factor( def get_property_unit( kitem_id: "Union[str, UUID]", property_name: str, - is_hdf5_column: bool = False, + is_dataframe_column: bool = False, autocomplete_symbol: bool = True, ) -> "Dict[str, Any]": """ @@ -78,7 +78,7 @@ def get_property_unit( kitem (KItem): The identifier of the KItem. property_name (str): The name of the property of the KItem for which the unit is to be retrieved. - is_hdf5_column (bool, optional): Indicates whether the property is an HDF5 + is_dataframe_column (bool, optional): Indicates whether the property is an DataFrame column or a custom property. Defaults to False. autocomplete_symbol (bool, optional): Whether the symbol of a unit shall be fetched automatically from the ontology when it is not given next to the @@ -103,7 +103,7 @@ def get_property_unit( query = units_sparql_object( kitem_id=kitem_id, property_name=property_name, - is_hdf5_column=is_hdf5_column, + is_dataframe_column=is_dataframe_column, autocomplete_symbol=autocomplete_symbol, ) except Exception as error: diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index e177b67..a45bea1 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -280,7 +280,7 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: "kitem_apps", "created_at", "external_links", - "hdf5", + "dataframe", }, exclude_none=True, ) @@ -508,16 +508,16 @@ def _commit_updated(buffer: "Dict[str, KItem]") -> None: old_kitem, ) if old_kitem: - if isinstance(new_kitem.hdf5, pd.DataFrame): + if isinstance(new_kitem.dataframe, pd.DataFrame): logger.debug( - "New KItem data has `pd.DataFrame`. Will push as hdf5." + "New KItem data has `pd.DataFrame`. Will push as dataframe." ) - _update_hdf5(new_kitem.id, new_kitem.hdf5) - new_kitem.hdf5 = _inspect_hdf5(new_kitem.id) - elif isinstance(new_kitem.hdf5, type(None)) and _inspect_hdf5( - new_kitem.id - ): - _delete_hdf5(new_kitem.id) + _update_dataframe(new_kitem.id, new_kitem.dataframe) + new_kitem.dataframe = _inspect_dataframe(new_kitem.id) + elif isinstance( + new_kitem.dataframe, type(None) + ) and _inspect_dataframe(new_kitem.id): + _delete_dataframe(new_kitem.id) _update_kitem(new_kitem, old_kitem) _update_attachments(new_kitem, old_kitem) if new_kitem.avatar.file or new_kitem.avatar.include_qr: @@ -539,7 +539,7 @@ def _commit_updated(buffer: "Dict[str, KItem]") -> None: def _commit_deleted(buffer: "Dict[str, KItem]") -> None: """Commit the buffer for the `deleted` buffers""" for kitem in buffer.values(): - _delete_hdf5(kitem.id) + _delete_dataframe(kitem.id) _delete_kitem(kitem) @@ -614,8 +614,8 @@ def _slug_is_available(ktype_id: Union[str, UUID], value: str) -> bool: return response.status_code == 404 -def _get_hdf5_column(kitem_id: str, column_id: int) -> List[Any]: - """Download the column of a hdf5 container of a certain kitem""" +def _get_dataframe_column(kitem_id: str, column_id: int) -> List[Any]: + """Download the column of a dataframe container of a certain kitem""" response = _perform_request( f"api/knowledge/data_api/{kitem_id}/column-{column_id}", "get" @@ -627,21 +627,21 @@ def _get_hdf5_column(kitem_id: str, column_id: int) -> List[Any]: return response.json().get("array") -def _inspect_hdf5(kitem_id: str) -> Optional[List[Dict[str, Any]]]: - """Get column info for the hdf5 container of a certain kitem""" +def _inspect_dataframe(kitem_id: str) -> Optional[List[Dict[str, Any]]]: + """Get column info for the dataframe container of a certain kitem""" response = _perform_request(f"api/knowledge/data_api/{kitem_id}", "get") if not response.ok and response.status_code == 404: - hdf5 = None + dataframe = None elif not response.ok and response.status_code != 404: message = f"""Something went wrong fetching intospection for kitem `{kitem_id}`: {response.text}""" raise ValueError(message) else: - hdf5 = response.json() - return hdf5 + dataframe = response.json() + return dataframe -def _update_hdf5(kitem_id: str, data: pd.DataFrame): +def _update_dataframe(kitem_id: str, data: pd.DataFrame): buffer = io.BytesIO() data.to_json(buffer, indent=2) buffer.seek(0) @@ -654,8 +654,8 @@ def _update_hdf5(kitem_id: str, data: pd.DataFrame): ) -def _delete_hdf5(kitem_id: str) -> Response: - logger.debug("Delete HDF5 for kitem with id `%s`.", kitem_id) +def _delete_dataframe(kitem_id: str) -> Response: + logger.debug("Delete DataFrame for kitem with id `%s`.", kitem_id) return _perform_request(f"api/knowledge/data_api/{kitem_id}", "delete") diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 8f8961d..65a64d6 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -84,8 +84,12 @@ "KTypes.App\n", "KTypes.Dataset\n", "KTypes.DatasetCatalog\n", - "KTypes.TestingMachine\n", - "KTypes.Expert\n" + "KTypes.Expert\n", + "KTypes.Test\n", + "KTypes.Specimen\n", + "KTypes.Batch\n", + "KTypes.Resource\n", + "KTypes.TestingMachine\n" ] } ], @@ -120,13 +124,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", + "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", "\n", "\tktype_id = KTypes.Dataset, \n", "\n", "\tin_backend = False, \n", "\n", - "\tslug = foo123-6e63e2f4, \n", + "\tslug = foo123-f0ff54d4, \n", "\n", "\tannotations = [], \n", "\n", @@ -158,7 +162,7 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None, \n", + "\tdataframe = None, \n", "\n", "\trdf_exists = False\n", ")" @@ -194,7 +198,7 @@ { "data": { "text/plain": [ - "'https://bue.materials-data.space/knowledge/dataset/foo123-6e63e2f4'" + "'https://bue.materials-data.space/knowledge/dataset/foo123-f0ff54d4'" ] }, "execution_count": 5, @@ -226,13 +230,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", + "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-6e63e2f4, \n", + "\tslug = foo123-f0ff54d4, \n", "\n", "\tannotations = [], \n", "\n", @@ -244,7 +248,7 @@ "\n", "\tauthors = [\n", "\t\t{\n", - "\t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", "\t\t}\n", "\t], \n", "\n", @@ -252,9 +256,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-05-13 11:52:26.331260, \n", + "\tcreated_at = 2024-08-01 15:17:05.206635, \n", "\n", - "\tupdated_at = 2024-05-13 11:52:26.331260, \n", + "\tupdated_at = 2024-08-01 15:17:05.206635, \n", "\n", "\texternal_links = [], \n", "\n", @@ -268,7 +272,7 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None, \n", + "\tdataframe = None, \n", "\n", "\trdf_exists = False\n", ")" @@ -348,19 +352,20 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", + "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-6e63e2f4, \n", + "\tslug = foo123-f0ff54d4, \n", "\n", "\tannotations = [\n", "\t\t{\n", "\t\t\tiri: www.example.org/foo,\n", "\t\t\tname: foo,\n", - "\t\t\tnamespace: www.example.org\n", + "\t\t\tnamespace: www.example.org,\n", + "\t\t\tdescription: None\n", "\t\t}\n", "\t], \n", "\n", @@ -380,7 +385,7 @@ "\n", "\tauthors = [\n", "\t\t{\n", - "\t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", "\t\t}\n", "\t], \n", "\n", @@ -394,9 +399,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-05-13 11:52:26.331260, \n", + "\tcreated_at = 2024-08-01 15:17:05.206635, \n", "\n", - "\tupdated_at = 2024-05-13 11:52:29.850682, \n", + "\tupdated_at = 2024-08-01 15:17:09.975357, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -420,7 +425,7 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None, \n", + "\tdataframe = None, \n", "\n", "\trdf_exists = False\n", ")" @@ -544,8 +549,10 @@ "data": { "text/plain": [ "{\n", - "\t\t\tname: foogroup,\n", - "\t\t\tgroup_id: 123\n", + "\t\t\tiri: www.example.org/foo,\n", + "\t\t\tname: foo,\n", + "\t\t\tnamespace: www.example.org,\n", + "\t\t\tdescription: None\n", "\t\t}" ] }, @@ -610,13 +617,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 6e63e2f4-587b-46a2-871e-fa835d219ba8, \n", + "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-6e63e2f4, \n", + "\tslug = foo123-f0ff54d4, \n", "\n", "\tannotations = [], \n", "\n", @@ -632,7 +639,7 @@ "\n", "\tauthors = [\n", "\t\t{\n", - "\t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", "\t\t}\n", "\t], \n", "\n", @@ -646,9 +653,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-05-13 11:52:26.331260, \n", + "\tcreated_at = 2024-08-01 15:17:05.206635, \n", "\n", - "\tupdated_at = 2024-05-13 11:52:29.850682, \n", + "\tupdated_at = 2024-08-01 15:17:09.975357, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -667,7 +674,7 @@ "\t\tfoo: bar\n", "\t}, \n", "\n", - "\thdf5 = None, \n", + "\tdataframe = None, \n", "\n", "\trdf_exists = False\n", ")" @@ -781,98 +788,56 @@ "text/plain": [ "[SearchResult(hit=KItem(\n", " \n", - " \tname = 3D-Blechmodelle2, \n", + " \tname = foo 1, \n", " \n", - " \tid = 493a9075-c30c-48c2-b9d6-2da408f0ecda, \n", + " \tid = 056dc8e1-ab8d-47b9-a873-0ee603727ed2, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = 3d-blechmodelle2-493a9075, \n", + " \tslug = foo1-056dc8e1, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: aaabc3d4-d06e-4512-9fe3-0d01f815690e,\n", - " \t\t\tname: DX56_D_FZ2_WR00_43,\n", - " \t\t\tslug: dx56_d_fz2_wr00_43-aaabc3d4,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DX56D,\n", - " \t\t\tname: DX56D,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", - " \t\t\tname: TensileTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", - " \t\t}, {\n", - " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'aaabc3d4-d06e-4512-9fe3-0d01f815690e', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DX56D', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'wes', 'SampleIdentifier-2': 'DX56_D_FZ2_WR00_43', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.55, 'SpecimenWidth': 20.04, 'TestingRate': 0.1, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", - " \t\t\tcreated_at: 2024-05-06T15:03:11.134388,\n", - " \t\t\tupdated_at: 2024-05-06T15:03:11.134388\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 57fe5c58-38ab-4c8a-8f82-efce9422c291,\n", - " \t\t\tname: DP800_F_FZ2_WR15_97,\n", - " \t\t\tslug: dp800_f_fz2_wr15_97-57fe5c58,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", + " \t\t\n", + " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tname: foo 2\n", + " \t\t\tslug: foo2-de8f99cf\n", + " \t\t\tktype_id: organization\n", + " \t\t\tsummary: None\n", + " \t\t\tavatar_exists: False\n", " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DP800,\n", - " \t\t\tname: DP800,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", - " \t\t\tname: TensileTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", - " \t\t}, {\n", - " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: DP800_F_FZ2_WR15_97.csv\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '57fe5c58-38ab-4c8a-8f82-efce9422c291', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DP800', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'fuchs', 'SampleIdentifier-2': 'DP800_F_FZ2_WR15_97', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.513, 'SpecimenWidth': 20.015, 'TestingRate': 0.24, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", - " \t\t\tcreated_at: 2024-05-06T15:03:46.647966,\n", - " \t\t\tupdated_at: 2024-05-06T15:03:46.647966\n", - " \t\t}\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org,\n", + " \t\t\tdescription: None\n", + " \t\t}]\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t}]\n", + " \t\t\texternal_links: []\n", + " \t\t\tcontacts: []\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}]\n", + " \t\t\tlinked_affiliations: []\n", + " \t\t\tattachments: []\n", + " \t\t\tuser_groups: []\n", + " \t\t\tcustom_properties: None\n", + " \t\t\tcreated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\tupdated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", " \t\t}\n", " \t], \n", " \n", @@ -880,9 +845,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-05-06 16:53:49.061347, \n", + " \tcreated_at = 2024-08-01 15:17:21.860032, \n", " \n", - " \tupdated_at = 2024-05-06 16:53:49.061347, \n", + " \tupdated_at = 2024-08-01 15:17:21.860032, \n", " \n", " \texternal_links = [], \n", " \n", @@ -894,61 +859,88 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None, \n", + " \tdataframe = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", + " ), fuzzy=False)]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` and `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[SearchResult(hit=KItem(\n", " \n", " \tname = foo 1, \n", " \n", - " \tid = c60cca3e-1d14-4e8e-ad2d-92bda7f975e5, \n", + " \tid = 056dc8e1-ab8d-47b9-a873-0ee603727ed2, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-c60cca3e, \n", + " \tslug = foo1-056dc8e1, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe,\n", - " \t\t\tname: foo 2,\n", - " \t\t\tslug: foo2-56791862,\n", - " \t\t\tktype_id: organization,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", + " \t\t\n", + " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tname: foo 2\n", + " \t\t\tslug: foo2-de8f99cf\n", + " \t\t\tktype_id: organization\n", + " \t\t\tsummary: None\n", + " \t\t\tavatar_exists: False\n", " \t\t\tannotations: [{\n", " \t\t\tiri: www.example.org/foo,\n", " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:49:54.440708,\n", - " \t\t\tupdated_at: 2024-05-13T11:49:54.440708\n", - " \t\t}\n", + " \t\t\tnamespace: www.example.org,\n", + " \t\t\tdescription: None\n", + " \t\t}]\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t}]\n", + " \t\t\texternal_links: []\n", + " \t\t\tcontacts: []\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}]\n", + " \t\t\tlinked_affiliations: []\n", + " \t\t\tattachments: []\n", + " \t\t\tuser_groups: []\n", + " \t\t\tcustom_properties: None\n", + " \t\t\tcreated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\tupdated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", " \t\t}\n", " \t], \n", " \n", @@ -956,9 +948,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-05-13 11:49:53.852559, \n", + " \tcreated_at = 2024-08-01 15:17:21.860032, \n", " \n", - " \tupdated_at = 2024-05-13 11:49:53.852559, \n", + " \tupdated_at = 2024-08-01 15:17:21.860032, \n", " \n", " \texternal_links = [], \n", " \n", @@ -970,1119 +962,64 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None, \n", + " \tdataframe = None, \n", " \n", " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = Crashartig, \n", + " \tname = foo 2, \n", " \n", - " \tid = 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad, \n", + " \tid = de8f99cf-005e-4c5d-9710-c313ce12b55c, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = crashartig-6d09ce07, \n", + " \tslug = foo2-de8f99cf, \n", " \n", - " \tannotations = [], \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/foo,\n", + " \t\t\tname: foo,\n", + " \t\t\tnamespace: www.example.org,\n", + " \t\t\tdescription: None\n", + " \t\t}\n", + " \t], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 84c23b6a-508a-4148-a224-fcefce65510e,\n", - " \t\t\tname: YS1-FzR4-D1Q,\n", - " \t\t\tslug: ys1-fzr4-d1q-84c23b6a,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '84c23b6a-508a-4148-a224-fcefce65510e', 'content': {'TimeStamp': '2013-06-03 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.083616, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.029, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:04:21.918404,\n", - " \t\t\tupdated_at: 2024-05-06T15:04:21.918404\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 11f2533e-f999-4151-85e6-e4821ce94c7e,\n", - " \t\t\tname: YS1-FzR4-D2Q,\n", - " \t\t\tslug: ys1-fzr4-d2q-11f2533e,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '11f2533e-f999-4151-85e6-e4821ce94c7e', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.044512000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.003, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:05:34.366982,\n", - " \t\t\tupdated_at: 2024-05-06T15:05:34.366982\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 19422f63-1087-47e4-9476-5def1ad264a4,\n", - " \t\t\tname: YS1-FzR4-D3Q,\n", - " \t\t\tslug: ys1-fzr4-d3q-19422f63,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '19422f63-1087-47e4-9476-5def1ad264a4', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.060554, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.502, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:06:47.134938,\n", - " \t\t\tupdated_at: 2024-05-06T15:06:47.134938\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 37989929-f27f-466a-b06b-55777cfa9c21,\n", - " \t\t\tname: YS1-FzR4-D5Q,\n", - " \t\t\tslug: ys1-fzr4-d5q-37989929,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D5Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '37989929-f27f-466a-b06b-55777cfa9c21', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.082055999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.507, 'OriginalWidth': 10.008, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:07:57.132627,\n", - " \t\t\tupdated_at: 2024-05-06T15:07:57.132627\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 2d31e9eb-3b88-497b-a69c-1dca43678841,\n", - " \t\t\tname: YS1-FzR4-D6Q,\n", - " \t\t\tslug: ys1-fzr4-d6q-2d31e9eb,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D6Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '2d31e9eb-3b88-497b-a69c-1dca43678841', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.080607999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D6L', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:09:05.808242,\n", - " \t\t\tupdated_at: 2024-05-06T15:09:05.808242\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 6ebe0e55-3904-440b-98c0-7d91fac824a9,\n", - " \t\t\tname: YS1-FzR4-D7Q,\n", - " \t\t\tslug: ys1-fzr4-d7q-6ebe0e55,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D7Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '6ebe0e55-3904-440b-98c0-7d91fac824a9', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.028012, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.501, 'OriginalWidth': 10.012, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:10:13.892563,\n", - " \t\t\tupdated_at: 2024-05-06T15:10:13.892563\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 4697d3ff-dbb1-4574-9b30-a8bb28ba9fee,\n", - " \t\t\tname: YS1-FzR4-S1Q,\n", - " \t\t\tslug: ys1-fzr4-s1q-4697d3ff,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", - " \t\t\tname: H340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '4697d3ff-dbb1-4574-9b30-a8bb28ba9fee', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.04996, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.04, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:11:21.835900,\n", - " \t\t\tupdated_at: 2024-05-06T15:11:21.835900\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b51b724c-4030-4767-9b2e-76b5548c078f,\n", - " \t\t\tname: YS1-FzR4-S2Q,\n", - " \t\t\tslug: ys1-fzr4-s2q-b51b724c,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", - " \t\t\tname: H340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'b51b724c-4030-4767-9b2e-76b5548c078f', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.027936, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.498, 'OriginalWidth': 10.032, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:11:54.654278,\n", - " \t\t\tupdated_at: 2024-05-06T15:11:54.654278\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 12bed326-a605-46f4-82f3-ae980a7a0548,\n", - " \t\t\tname: YS1-FzR4-S3Q,\n", - " \t\t\tslug: ys1-fzr4-s3q-12bed326,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", - " \t\t\tname: H340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '12bed326-a605-46f4-82f3-ae980a7a0548', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.025976, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.024, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:12:27.310845,\n", - " \t\t\tupdated_at: 2024-05-06T15:12:27.310845\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 31566fde-4356-4c3f-ae2a-63c1a7e8da0d,\n", - " \t\t\tname: YS2-FzR4-D1Q,\n", - " \t\t\tslug: ys2-fzr4-d1q-31566fde,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '31566fde-4356-4c3f-ae2a-63c1a7e8da0d', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.397029999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.529, 'OriginalWidth': 10.07, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:12:59.925719,\n", - " \t\t\tupdated_at: 2024-05-06T15:12:59.925719\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 13c64610-8251-443e-9560-4d2297a10543,\n", - " \t\t\tname: YS2-FzR4-D2Q,\n", - " \t\t\tslug: ys2-fzr4-d2q-13c64610,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '13c64610-8251-443e-9560-4d2297a10543', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.316811, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.523, 'OriginalWidth': 10.057, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:14:10.649454,\n", - " \t\t\tupdated_at: 2024-05-06T15:14:10.649454\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b704fa17-83f4-434d-8807-14b201f35659,\n", - " \t\t\tname: YS2-FzR4-D3Q,\n", - " \t\t\tslug: ys2-fzr4-d3q-b704fa17,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'b704fa17-83f4-434d-8807-14b201f35659', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.366201, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.527, 'OriginalWidth': 10.063, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:15:23.600966,\n", - " \t\t\tupdated_at: 2024-05-06T15:15:23.600966\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 7dd2b925-7bf7-4cc0-9a78-aac7342aa283,\n", - " \t\t\tname: YS2-FzR4-D5Q,\n", - " \t\t\tslug: ys2-fzr4-d5q-7dd2b925,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D5Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '7dd2b925-7bf7-4cc0-9a78-aac7342aa283', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.351816000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.528, 'OriginalWidth': 10.047, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:16:32.952408,\n", - " \t\t\tupdated_at: 2024-05-06T15:16:32.952408\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 14b75028-fb80-4e11-8419-b4f00dd365fa,\n", - " \t\t\tname: YS2-FzR4-D6Q,\n", - " \t\t\tslug: ys2-fzr4-d6q-14b75028,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D6Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '14b75028-fb80-4e11-8419-b4f00dd365fa', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.38894, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D6Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.532, 'OriginalWidth': 10.045, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:17:43.978073,\n", - " \t\t\tupdated_at: 2024-05-06T15:17:43.978073\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 1b091b58-9aa0-4c52-9aa3-d2a571317f88,\n", - " \t\t\tname: YS2-FzR4-D7Q,\n", - " \t\t\tslug: ys2-fzr4-d7q-1b091b58,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D7Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '1b091b58-9aa0-4c52-9aa3-d2a571317f88', 'content': {'TimeStamp': '2013-05-15 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.282796, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.516, 'OriginalWidth': 10.081, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:18:50.363842,\n", - " \t\t\tupdated_at: 2024-05-06T15:18:50.363842\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 672ecbda-47a2-4ec7-88af-c149eaceb8df,\n", - " \t\t\tname: YS2-FzR4-S1Q,\n", - " \t\t\tslug: ys2-fzr4-s1q-672ecbda,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '672ecbda-47a2-4ec7-88af-c149eaceb8df', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.490034999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.065, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:20:00.154967,\n", - " \t\t\tupdated_at: 2024-05-06T15:20:00.154967\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: c9c465e0-0e0b-4fca-8e05-e8750a8bbe82,\n", - " \t\t\tname: YS2-FzR4-S2Q,\n", - " \t\t\tslug: ys2-fzr4-s2q-c9c465e0,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'c9c465e0-0e0b-4fca-8e05-e8750a8bbe82', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.408183, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.533, 'OriginalWidth': 10.051, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:20:34.725996,\n", - " \t\t\tupdated_at: 2024-05-06T15:20:34.725996\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 495e379c-1966-4055-8b9f-aa9070de88ef,\n", - " \t\t\tname: YS2-FzR4-S3Q,\n", - " \t\t\tslug: ys2-fzr4-s3q-495e379c,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '495e379c-1966-4055-8b9f-aa9070de88ef', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.448482, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.038, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:21:06.436822,\n", - " \t\t\tupdated_at: 2024-05-06T15:21:06.436822\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 8f9ffd25-f337-422d-a7ad-964c0fe48a97,\n", - " \t\t\tname: YS1-Sz0°-S1L,\n", - " \t\t\tslug: ys1-sz0-s1l-8f9ffd25,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S1L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '8f9ffd25-f337-422d-a7ad-964c0fe48a97', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.548296, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1L', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:21:39.354093,\n", - " \t\t\tupdated_at: 2024-05-06T15:21:39.354093\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 7eb23c15-88f2-42c9-8b78-a406733495fb,\n", - " \t\t\tname: YS1-Sz0°-S1Q,\n", - " \t\t\tslug: ys1-sz0-s1q-7eb23c15,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '7eb23c15-88f2-42c9-8b78-a406733495fb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.007, 'OriginalCrosssection': 10.475465, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1Q', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:22:08.507191,\n", - " \t\t\tupdated_at: 2024-05-06T15:22:08.507191\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 9206d49b-71e9-4b51-80d2-cbd67f03cbbd,\n", - " \t\t\tname: YS1-Sz0°-S2L,\n", - " \t\t\tslug: ys1-sz0-s2l-9206d49b,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S2L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '9206d49b-71e9-4b51-80d2-cbd67f03cbbd', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.517325000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:22:38.075277,\n", - " \t\t\tupdated_at: 2024-05-06T15:22:38.075277\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 4471f17e-3f9c-4f70-b9dc-cf1c6894a40b,\n", - " \t\t\tname: YS1-Sz0°-S2Q,\n", - " \t\t\tslug: ys1-sz0-s2q-4471f17e,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '4471f17e-3f9c-4f70-b9dc-cf1c6894a40b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.01, 'OriginalCrosssection': 10.47294, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2Q', 'OriginalThickness': 1.494, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:23:07.127597,\n", - " \t\t\tupdated_at: 2024-05-06T15:23:07.127597\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: e9f8df8f-201f-440d-9971-1634cb56bdbb,\n", - " \t\t\tname: YS1-Sz0°-S3L,\n", - " \t\t\tslug: ys1-sz0-s3l-e9f8df8f,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S3L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'e9f8df8f-201f-440d-9971-1634cb56bdbb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.043, 'OriginalCrosssection': 10.529285000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:23:36.615187,\n", - " \t\t\tupdated_at: 2024-05-06T15:23:36.615187\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: e05b3406-84c3-4ce8-ae95-5678d95e9e20,\n", - " \t\t\tname: YS1-Sz0°-S3Q,\n", - " \t\t\tslug: ys1-sz0-s3q-e05b3406,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'e05b3406-84c3-4ce8-ae95-5678d95e9e20', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100025.0, 'Radius': 2.0, 'ShearLength': 7.019, 'OriginalCrosssection': 10.479367000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3Q', 'OriginalThickness': 1.493, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:24:05.800555,\n", - " \t\t\tupdated_at: 2024-05-06T15:24:05.800555\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 994550a4-0649-4d66-b6bb-ddce7298b338,\n", - " \t\t\tname: YS1-Sz0°-S4Q,\n", - " \t\t\tslug: ys1-sz0-s4q-994550a4,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S4Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '994550a4-0649-4d66-b6bb-ddce7298b338', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.515384, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S4Q', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:24:35.125022,\n", - " \t\t\tupdated_at: 2024-05-06T15:24:35.125022\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 89eea4e9-4286-4a30-b41f-572841f4e10b,\n", - " \t\t\tname: YS2-Sz0°-S1L,\n", - " \t\t\tslug: ys2-sz0-s1l-89eea4e9,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S1L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '89eea4e9-4286-4a30-b41f-572841f4e10b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.618806000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1L', 'OriginalThickness': 1.506, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:25:04.777217,\n", - " \t\t\tupdated_at: 2024-05-06T15:25:04.777217\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: e26d2a56-5782-4605-9b14-46d5a809b5a9,\n", - " \t\t\tname: YS2-Sz0°-S1Q,\n", - " \t\t\tslug: ys2-sz0-s1q-e26d2a56,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'e26d2a56-5782-4605-9b14-46d5a809b5a9', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.032, 'OriginalCrosssection': 10.625352, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1Q', 'OriginalThickness': 1.511, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:25:34.941942,\n", - " \t\t\tupdated_at: 2024-05-06T15:25:34.941942\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 6acadb27-9804-47f5-9a59-affe2ba82e77,\n", - " \t\t\tname: YS2-Sz0°-S2L,\n", - " \t\t\tslug: ys2-sz0-s2l-6acadb27,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S2L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '6acadb27-9804-47f5-9a59-affe2ba82e77', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.036, 'OriginalCrosssection': 10.568071999999999, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2L', 'OriginalThickness': 1.502, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:26:05.507889,\n", - " \t\t\tupdated_at: 2024-05-06T15:26:05.507889\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 829c4272-64dd-4089-8aab-94fda9b9aec5,\n", - " \t\t\tname: YS2-Sz0°-S2Q,\n", - " \t\t\tslug: ys2-sz0-s2q-829c4272,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '829c4272-64dd-4089-8aab-94fda9b9aec5', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.634877, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2Q', 'OriginalThickness': 1.513, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:26:35.271807,\n", - " \t\t\tupdated_at: 2024-05-06T15:26:35.271807\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 5069d37a-4ede-42b0-bf58-542581e37f84,\n", - " \t\t\tname: YS2-Sz0°-S3L,\n", - " \t\t\tslug: ys2-sz0-s3l-5069d37a,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S3L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '5069d37a-4ede-42b0-bf58-542581e37f84', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.049, 'OriginalCrosssection': 10.580549, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3L', 'OriginalThickness': 1.501, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:27:05.317794,\n", - " \t\t\tupdated_at: 2024-05-06T15:27:05.317794\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b0e5f8b6-192e-4713-96ca-6a023cf3af76,\n", - " \t\t\tname: YS2-Sz0°-S3Q,\n", - " \t\t\tslug: ys2-sz0-s3q-b0e5f8b6,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'b0e5f8b6-192e-4713-96ca-6a023cf3af76', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.65099, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3Q', 'OriginalThickness': 1.514, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:27:35.118023,\n", - " \t\t\tupdated_at: 2024-05-06T15:27:35.118023\n", - " \t\t}\n", + " \t\t\n", + " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tname: foo 1\n", + " \t\t\tslug: foo1-056dc8e1\n", + " \t\t\tktype_id: dataset-catalog\n", + " \t\t\tsummary: None\n", + " \t\t\tavatar_exists: False\n", + " \t\t\tannotations: []\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t}]\n", + " \t\t\texternal_links: []\n", + " \t\t\tcontacts: []\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}]\n", + " \t\t\tlinked_affiliations: []\n", + " \t\t\tattachments: []\n", + " \t\t\tuser_groups: []\n", + " \t\t\tcustom_properties: None\n", + " \t\t\tcreated_at: 2024-08-01T15:17:21.860032\n", + " \t\t\tupdated_at: 2024-08-01T15:17:21.860032\n", + " \t\t\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", " \t\t}\n", " \t], \n", " \n", @@ -2090,9 +1027,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-05-06 16:53:59.576957, \n", + " \tcreated_at = 2024-08-01 15:17:22.446849, \n", " \n", - " \tupdated_at = 2024-05-06 16:53:59.576957, \n", + " \tupdated_at = 2024-08-01 15:17:22.446849, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2104,61 +1041,88 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None, \n", + " \tdataframe = None, \n", " \n", " \trdf_exists = False\n", " ), fuzzy=False),\n", " SearchResult(hit=KItem(\n", " \n", - " \tname = foo 1, \n", + " \tname = foo 3, \n", " \n", - " \tid = e3487b03-e96a-49a4-bcb1-6599802ada8f, \n", + " \tid = e61ce089-a330-411b-bd9c-1e3c379d7f7a, \n", " \n", - " \tktype_id = dataset-catalog, \n", + " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-e3487b03, \n", + " \tslug = foo3-e61ce089, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", - " \tlinked_kitems = [\n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", " \t\t{\n", - " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f,\n", - " \t\t\tname: foo 2,\n", - " \t\t\tslug: foo2-3975cd66,\n", - " \t\t\tktype_id: organization,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:52:41.117372,\n", - " \t\t\tupdated_at: 2024-05-13T11:52:41.117372\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-08-01 15:17:23.003614, \n", + " \n", + " \tupdated_at = 2024-08-01 15:17:23.003614, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \tdataframe = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", + " \n", + " \tname = foo 4, \n", + " \n", + " \tid = f8fce715-2104-429f-8b2e-e2083c93b1ae, \n", + " \n", + " \tktype_id = organization, \n", + " \n", + " \tin_backend = True, \n", + " \n", + " \tslug = foo4-f8fce715, \n", + " \n", + " \tannotations = [\n", + " \t\t{\n", + " \t\t\tiri: www.example.org/bar,\n", + " \t\t\tname: bar,\n", + " \t\t\tnamespace: www.example.org,\n", + " \t\t\tdescription: None\n", " \t\t}\n", " \t], \n", " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", " \t\t}\n", " \t], \n", " \n", @@ -2166,9 +1130,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-05-13 11:52:40.683110, \n", + " \tcreated_at = 2024-08-01 15:17:23.571040, \n", " \n", - " \tupdated_at = 2024-05-13 11:52:40.683110, \n", + " \tupdated_at = 2024-08-01 15:17:23.571040, \n", " \n", " \texternal_links = [], \n", " \n", @@ -2180,31 +1144,31 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None, \n", + " \tdataframe = None, \n", " \n", " \trdf_exists = False\n", " ), fuzzy=False)]" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "dsms.search(ktypes=[dsms.ktypes.DatasetCatalog])" + "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "... and for all of type `Organization` and `DatasetCatalog`:" + "... or for all of type `DatasetCatalog` with `foo` in the name:" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -2212,1922 +1176,56 @@ "text/plain": [ "[SearchResult(hit=KItem(\n", " \n", - " \tname = 3D-Blechmodelle2, \n", + " \tname = foo 1, \n", " \n", - " \tid = 493a9075-c30c-48c2-b9d6-2da408f0ecda, \n", + " \tid = 056dc8e1-ab8d-47b9-a873-0ee603727ed2, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = 3d-blechmodelle2-493a9075, \n", + " \tslug = foo1-056dc8e1, \n", " \n", " \tannotations = [], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: aaabc3d4-d06e-4512-9fe3-0d01f815690e,\n", - " \t\t\tname: DX56_D_FZ2_WR00_43,\n", - " \t\t\tslug: dx56_d_fz2_wr00_43-aaabc3d4,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DX56D,\n", - " \t\t\tname: DX56D,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", - " \t\t\tname: TensileTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", - " \t\t}, {\n", - " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: DX56_D_FZ2_WR00_43.csv\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'aaabc3d4-d06e-4512-9fe3-0d01f815690e', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DX56D', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'wes', 'SampleIdentifier-2': 'DX56_D_FZ2_WR00_43', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.55, 'SpecimenWidth': 20.04, 'TestingRate': 0.1, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", - " \t\t\tcreated_at: 2024-05-06T15:03:11.134388,\n", - " \t\t\tupdated_at: 2024-05-06T15:03:11.134388\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 57fe5c58-38ab-4c8a-8f82-efce9422c291,\n", - " \t\t\tname: DP800_F_FZ2_WR15_97,\n", - " \t\t\tslug: dp800_f_fz2_wr15_97-57fe5c58,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/DP800,\n", - " \t\t\tname: DP800,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/TensileTest,\n", - " \t\t\tname: TensileTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 493a9075-c30c-48c2-b9d6-2da408f0ecda\n", - " \t\t}, {\n", - " \t\t\tid: 02db2d4a-e6a3-4187-95f3-923f058ded2c\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: DP800_F_FZ2_WR15_97.csv\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '57fe5c58-38ab-4c8a-8f82-efce9422c291', 'content': {'TestingFacility': 'Fraunhofer IWM', 'ProjectNumber': 142003.0, 'ProjectName': '3D-Blechmodelle2', 'TimeStamp': '16.04.2016 13:53', 'TestingMachine': 'ZwickRoell Kappa50DS', 'ForceMeasuringDevice': 'xForce K', 'DisplacementTransducer': 'makroXtens', 'TestStandard': 'DIN EN ISO 6892-1', 'Material': 'DP800', 'SpecimenType': 'FZ2 (L0=80_b0=20_R20)', 'Tester': 'fuchs', 'SampleIdentifier-2': 'DP800_F_FZ2_WR15_97', 'OriginalGaugeLength': 80.0, 'ParallelLength': 120.0, 'SpecimenThickness': 1.513, 'SpecimenWidth': 20.015, 'TestingRate': 0.24, 'Preload': 2.0, 'Temperature': 22.0, 'Remark': ''}},\n", - " \t\t\tcreated_at: 2024-05-06T15:03:46.647966,\n", - " \t\t\tupdated_at: 2024-05-06T15:03:46.647966\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-06 16:53:49.061347, \n", - " \n", - " \tupdated_at = 2024-05-06 16:53:49.061347, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = c60cca3e-1d14-4e8e-ad2d-92bda7f975e5, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo1-c60cca3e, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe,\n", - " \t\t\tname: foo 2,\n", - " \t\t\tslug: foo2-56791862,\n", - " \t\t\tktype_id: organization,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:49:54.440708,\n", - " \t\t\tupdated_at: 2024-05-13T11:49:54.440708\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:53.852559, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:53.852559, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 2, \n", - " \n", - " \tid = 56791862-dd99-4fdb-984a-f9741c7cfdfe, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo2-56791862, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5,\n", - " \t\t\tname: foo 1,\n", - " \t\t\tslug: foo1-c60cca3e,\n", - " \t\t\tktype_id: dataset-catalog,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:49:53.852559,\n", - " \t\t\tupdated_at: 2024-05-13T11:49:53.852559\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:54.440708, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:54.440708, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 3, \n", - " \n", - " \tid = 4eb3de3b-0d7a-4d5e-b6f6-60c40a746802, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo3-4eb3de3b, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:54.988706, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:54.988706, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 4, \n", - " \n", - " \tid = 9d541ea2-deb6-42e6-bd91-5778848e361e, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo4-9d541ea2, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/bar,\n", - " \t\t\tname: bar,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:55.546017, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:55.546017, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = Crashartig, \n", - " \n", - " \tid = 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = crashartig-6d09ce07, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 84c23b6a-508a-4148-a224-fcefce65510e,\n", - " \t\t\tname: YS1-FzR4-D1Q,\n", - " \t\t\tslug: ys1-fzr4-d1q-84c23b6a,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '84c23b6a-508a-4148-a224-fcefce65510e', 'content': {'TimeStamp': '2013-06-03 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.083616, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.029, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:04:21.918404,\n", - " \t\t\tupdated_at: 2024-05-06T15:04:21.918404\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 11f2533e-f999-4151-85e6-e4821ce94c7e,\n", - " \t\t\tname: YS1-FzR4-D2Q,\n", - " \t\t\tslug: ys1-fzr4-d2q-11f2533e,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '11f2533e-f999-4151-85e6-e4821ce94c7e', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.044512000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.003, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:05:34.366982,\n", - " \t\t\tupdated_at: 2024-05-06T15:05:34.366982\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 19422f63-1087-47e4-9476-5def1ad264a4,\n", - " \t\t\tname: YS1-FzR4-D3Q,\n", - " \t\t\tslug: ys1-fzr4-d3q-19422f63,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '19422f63-1087-47e4-9476-5def1ad264a4', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.060554, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.502, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:06:47.134938,\n", - " \t\t\tupdated_at: 2024-05-06T15:06:47.134938\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 37989929-f27f-466a-b06b-55777cfa9c21,\n", - " \t\t\tname: YS1-FzR4-D5Q,\n", - " \t\t\tslug: ys1-fzr4-d5q-37989929,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D5Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '37989929-f27f-466a-b06b-55777cfa9c21', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.082055999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.507, 'OriginalWidth': 10.008, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:07:57.132627,\n", - " \t\t\tupdated_at: 2024-05-06T15:07:57.132627\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 2d31e9eb-3b88-497b-a69c-1dca43678841,\n", - " \t\t\tname: YS1-FzR4-D6Q,\n", - " \t\t\tslug: ys1-fzr4-d6q-2d31e9eb,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D6Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '2d31e9eb-3b88-497b-a69c-1dca43678841', 'content': {'TimeStamp': '2013-05-29 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.080607999999998, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D6L', 'TestTemperature': 22.0, 'OriginalThickness': 1.504, 'OriginalWidth': 10.027, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:09:05.808242,\n", - " \t\t\tupdated_at: 2024-05-06T15:09:05.808242\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 6ebe0e55-3904-440b-98c0-7d91fac824a9,\n", - " \t\t\tname: YS1-FzR4-D7Q,\n", - " \t\t\tslug: ys1-fzr4-d7q-6ebe0e55,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ZStE340,\n", - " \t\t\tname: ZStE340,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: d3fba0aa-d2ca-4e7a-8f31-0f1cc74c0316\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-D7Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '6ebe0e55-3904-440b-98c0-7d91fac824a9', 'content': {'TimeStamp': '2013-06-04 00:00:00', 'TestingMachine': 'SZM 100', 'Material': 'ZStE340', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.028012, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.501, 'OriginalWidth': 10.012, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:10:13.892563,\n", - " \t\t\tupdated_at: 2024-05-06T15:10:13.892563\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 4697d3ff-dbb1-4574-9b30-a8bb28ba9fee,\n", - " \t\t\tname: YS1-FzR4-S1Q,\n", - " \t\t\tslug: ys1-fzr4-s1q-4697d3ff,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", - " \t\t\tname: H340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '4697d3ff-dbb1-4574-9b30-a8bb28ba9fee', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.04996, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.04, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:11:21.835900,\n", - " \t\t\tupdated_at: 2024-05-06T15:11:21.835900\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b51b724c-4030-4767-9b2e-76b5548c078f,\n", - " \t\t\tname: YS1-FzR4-S2Q,\n", - " \t\t\tslug: ys1-fzr4-s2q-b51b724c,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", - " \t\t\tname: H340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'b51b724c-4030-4767-9b2e-76b5548c078f', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.027936, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.498, 'OriginalWidth': 10.032, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:11:54.654278,\n", - " \t\t\tupdated_at: 2024-05-06T15:11:54.654278\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 12bed326-a605-46f4-82f3-ae980a7a0548,\n", - " \t\t\tname: YS1-FzR4-S3Q,\n", - " \t\t\tslug: ys1-fzr4-s3q-12bed326,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/H340LAD,\n", - " \t\t\tname: H340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-FzR4-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '12bed326-a605-46f4-82f3-ae980a7a0548', 'content': {'TimeStamp': '2013-06-13 00:00:00', 'TestingMachine': 'Instron 5985', 'Material': 'H340LAD', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.025976, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS1-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.499, 'OriginalWidth': 10.024, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:12:27.310845,\n", - " \t\t\tupdated_at: 2024-05-06T15:12:27.310845\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 31566fde-4356-4c3f-ae2a-63c1a7e8da0d,\n", - " \t\t\tname: YS2-FzR4-D1Q,\n", - " \t\t\tslug: ys2-fzr4-d1q-31566fde,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '31566fde-4356-4c3f-ae2a-63c1a7e8da0d', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.397029999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D1Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.529, 'OriginalWidth': 10.07, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:12:59.925719,\n", - " \t\t\tupdated_at: 2024-05-06T15:12:59.925719\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 13c64610-8251-443e-9560-4d2297a10543,\n", - " \t\t\tname: YS2-FzR4-D2Q,\n", - " \t\t\tslug: ys2-fzr4-d2q-13c64610,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '13c64610-8251-443e-9560-4d2297a10543', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.316811, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D2Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.523, 'OriginalWidth': 10.057, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:14:10.649454,\n", - " \t\t\tupdated_at: 2024-05-06T15:14:10.649454\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b704fa17-83f4-434d-8807-14b201f35659,\n", - " \t\t\tname: YS2-FzR4-D3Q,\n", - " \t\t\tslug: ys2-fzr4-d3q-b704fa17,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'b704fa17-83f4-434d-8807-14b201f35659', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.366201, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D3Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.527, 'OriginalWidth': 10.063, 'CrossheadSeparationRate': 25.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:15:23.600966,\n", - " \t\t\tupdated_at: 2024-05-06T15:15:23.600966\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 7dd2b925-7bf7-4cc0-9a78-aac7342aa283,\n", - " \t\t\tname: YS2-FzR4-D5Q,\n", - " \t\t\tslug: ys2-fzr4-d5q-7dd2b925,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D5Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '7dd2b925-7bf7-4cc0-9a78-aac7342aa283', 'content': {'TimeStamp': '2013-05-07 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.351816000000001, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D5Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.528, 'OriginalWidth': 10.047, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:16:32.952408,\n", - " \t\t\tupdated_at: 2024-05-06T15:16:32.952408\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 14b75028-fb80-4e11-8419-b4f00dd365fa,\n", - " \t\t\tname: YS2-FzR4-D6Q,\n", - " \t\t\tslug: ys2-fzr4-d6q-14b75028,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D6Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '14b75028-fb80-4e11-8419-b4f00dd365fa', 'content': {'TimeStamp': '2013-05-14 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.38894, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D6Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.532, 'OriginalWidth': 10.045, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:17:43.978073,\n", - " \t\t\tupdated_at: 2024-05-06T15:17:43.978073\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 1b091b58-9aa0-4c52-9aa3-d2a571317f88,\n", - " \t\t\tname: YS2-FzR4-D7Q,\n", - " \t\t\tslug: ys2-fzr4-d7q-1b091b58,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: f325b2ca-dfab-492a-a859-345cec1990c8\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-D7Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '1b091b58-9aa0-4c52-9aa3-d2a571317f88', 'content': {'TimeStamp': '2013-05-15 00:00:00', 'TestingMachine': 'SZM 500', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100025.0, 'Radius': 4.0, 'OriginalCrosssection': 15.282796, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-D7Q', 'TestTemperature': 22.0, 'OriginalThickness': 1.516, 'OriginalWidth': 10.081, 'CrossheadSeparationRate': 2500.0}},\n", - " \t\t\tcreated_at: 2024-05-06T15:18:50.363842,\n", - " \t\t\tupdated_at: 2024-05-06T15:18:50.363842\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 672ecbda-47a2-4ec7-88af-c149eaceb8df,\n", - " \t\t\tname: YS2-FzR4-S1Q,\n", - " \t\t\tslug: ys2-fzr4-s1q-672ecbda,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '672ecbda-47a2-4ec7-88af-c149eaceb8df', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.490034999999999, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S1Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.065, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:20:00.154967,\n", - " \t\t\tupdated_at: 2024-05-06T15:20:00.154967\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: c9c465e0-0e0b-4fca-8e05-e8750a8bbe82,\n", - " \t\t\tname: YS2-FzR4-S2Q,\n", - " \t\t\tslug: ys2-fzr4-s2q-c9c465e0,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'c9c465e0-0e0b-4fca-8e05-e8750a8bbe82', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.408183, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S2Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.533, 'OriginalWidth': 10.051, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:20:34.725996,\n", - " \t\t\tupdated_at: 2024-05-06T15:20:34.725996\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 495e379c-1966-4055-8b9f-aa9070de88ef,\n", - " \t\t\tname: YS2-FzR4-S3Q,\n", - " \t\t\tslug: ys2-fzr4-s3q-495e379c,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/NotchTest,\n", - " \t\t\tname: NotchTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HXT980X,\n", - " \t\t\tname: HXT980X,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 28c2bb71-eb91-423f-b73c-5c2cab51c410\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-FzR4-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '495e379c-1966-4055-8b9f-aa9070de88ef', 'content': {'TimeStamp': '2013-03-25 00:00:00', 'TestingMachine': 'Instron 1362', 'Material': 'HXT980X', 'ProjectName': 'Crashartig', 'ProjectNumber': 100056.0, 'Radius': 4.0, 'OriginalCrosssection': 15.448482, 'TestPieceGeometry': 'Notched Flat Tensile Specimen', 'SampleIdentifier': 'YS2-FzR4-S3Q', 'TestTemperature': 25.0, 'OriginalThickness': 1.539, 'OriginalWidth': 10.038, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:21:06.436822,\n", - " \t\t\tupdated_at: 2024-05-06T15:21:06.436822\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 8f9ffd25-f337-422d-a7ad-964c0fe48a97,\n", - " \t\t\tname: YS1-Sz0°-S1L,\n", - " \t\t\tslug: ys1-sz0-s1l-8f9ffd25,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S1L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '8f9ffd25-f337-422d-a7ad-964c0fe48a97', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.548296, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1L', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:21:39.354093,\n", - " \t\t\tupdated_at: 2024-05-06T15:21:39.354093\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 7eb23c15-88f2-42c9-8b78-a406733495fb,\n", - " \t\t\tname: YS1-Sz0°-S1Q,\n", - " \t\t\tslug: ys1-sz0-s1q-7eb23c15,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '7eb23c15-88f2-42c9-8b78-a406733495fb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.007, 'OriginalCrosssection': 10.475465, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S1Q', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:22:08.507191,\n", - " \t\t\tupdated_at: 2024-05-06T15:22:08.507191\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 9206d49b-71e9-4b51-80d2-cbd67f03cbbd,\n", - " \t\t\tname: YS1-Sz0°-S2L,\n", - " \t\t\tslug: ys1-sz0-s2l-9206d49b,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S2L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '9206d49b-71e9-4b51-80d2-cbd67f03cbbd', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.517325000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:22:38.075277,\n", - " \t\t\tupdated_at: 2024-05-06T15:22:38.075277\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 4471f17e-3f9c-4f70-b9dc-cf1c6894a40b,\n", - " \t\t\tname: YS1-Sz0°-S2Q,\n", - " \t\t\tslug: ys1-sz0-s2q-4471f17e,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '4471f17e-3f9c-4f70-b9dc-cf1c6894a40b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.01, 'OriginalCrosssection': 10.47294, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S2Q', 'OriginalThickness': 1.494, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:23:07.127597,\n", - " \t\t\tupdated_at: 2024-05-06T15:23:07.127597\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: e9f8df8f-201f-440d-9971-1634cb56bdbb,\n", - " \t\t\tname: YS1-Sz0°-S3L,\n", - " \t\t\tslug: ys1-sz0-s3l-e9f8df8f,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S3L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'e9f8df8f-201f-440d-9971-1634cb56bdbb', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.043, 'OriginalCrosssection': 10.529285000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3L', 'OriginalThickness': 1.495, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:23:36.615187,\n", - " \t\t\tupdated_at: 2024-05-06T15:23:36.615187\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: e05b3406-84c3-4ce8-ae95-5678d95e9e20,\n", - " \t\t\tname: YS1-Sz0°-S3Q,\n", - " \t\t\tslug: ys1-sz0-s3q-e05b3406,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'e05b3406-84c3-4ce8-ae95-5678d95e9e20', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100025.0, 'Radius': 2.0, 'ShearLength': 7.019, 'OriginalCrosssection': 10.479367000000002, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S3Q', 'OriginalThickness': 1.493, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:24:05.800555,\n", - " \t\t\tupdated_at: 2024-05-06T15:24:05.800555\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 994550a4-0649-4d66-b6bb-ddce7298b338,\n", - " \t\t\tname: YS1-Sz0°-S4Q,\n", - " \t\t\tslug: ys1-sz0-s4q-994550a4,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HX340LAD,\n", - " \t\t\tname: HX340LAD,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS1-Sz0-S4Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '994550a4-0649-4d66-b6bb-ddce7298b338', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HX340LAD', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.515384, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS1-Sz0°-S4Q', 'OriginalThickness': 1.496, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:24:35.125022,\n", - " \t\t\tupdated_at: 2024-05-06T15:24:35.125022\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 89eea4e9-4286-4a30-b41f-572841f4e10b,\n", - " \t\t\tname: YS2-Sz0°-S1L,\n", - " \t\t\tslug: ys2-sz0-s1l-89eea4e9,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S1L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '89eea4e9-4286-4a30-b41f-572841f4e10b', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.051, 'OriginalCrosssection': 10.618806000000001, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1L', 'OriginalThickness': 1.506, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:25:04.777217,\n", - " \t\t\tupdated_at: 2024-05-06T15:25:04.777217\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: e26d2a56-5782-4605-9b14-46d5a809b5a9,\n", - " \t\t\tname: YS2-Sz0°-S1Q,\n", - " \t\t\tslug: ys2-sz0-s1q-e26d2a56,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S1Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'e26d2a56-5782-4605-9b14-46d5a809b5a9', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.032, 'OriginalCrosssection': 10.625352, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S1Q', 'OriginalThickness': 1.511, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:25:34.941942,\n", - " \t\t\tupdated_at: 2024-05-06T15:25:34.941942\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 6acadb27-9804-47f5-9a59-affe2ba82e77,\n", - " \t\t\tname: YS2-Sz0°-S2L,\n", - " \t\t\tslug: ys2-sz0-s2l-6acadb27,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S2L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '6acadb27-9804-47f5-9a59-affe2ba82e77', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.036, 'OriginalCrosssection': 10.568071999999999, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2L', 'OriginalThickness': 1.502, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:26:05.507889,\n", - " \t\t\tupdated_at: 2024-05-06T15:26:05.507889\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 829c4272-64dd-4089-8aab-94fda9b9aec5,\n", - " \t\t\tname: YS2-Sz0°-S2Q,\n", - " \t\t\tslug: ys2-sz0-s2q-829c4272,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S2Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '829c4272-64dd-4089-8aab-94fda9b9aec5', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.029, 'OriginalCrosssection': 10.634877, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S2Q', 'OriginalThickness': 1.513, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:26:35.271807,\n", - " \t\t\tupdated_at: 2024-05-06T15:26:35.271807\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: 5069d37a-4ede-42b0-bf58-542581e37f84,\n", - " \t\t\tname: YS2-Sz0°-S3L,\n", - " \t\t\tslug: ys2-sz0-s3l-5069d37a,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S3L.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': '5069d37a-4ede-42b0-bf58-542581e37f84', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.049, 'OriginalCrosssection': 10.580549, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3L', 'OriginalThickness': 1.501, 'CrossheadSeparationRate': 0.01}},\n", - " \t\t\tcreated_at: 2024-05-06T15:27:05.317794,\n", - " \t\t\tupdated_at: 2024-05-06T15:27:05.317794\n", - " \t\t}, \n", - " \t\t{\n", - " \t\t\tid: b0e5f8b6-192e-4713-96ca-6a023cf3af76,\n", - " \t\t\tname: YS2-Sz0°-S3Q,\n", - " \t\t\tslug: ys2-sz0-s3q-b0e5f8b6,\n", - " \t\t\tktype_id: dataset,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/ShearTest,\n", - " \t\t\tname: ShearTest,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}, {\n", - " \t\t\tiri: https://w3id.org/steel/ProcessOntology/HCT980X+Z110MB,\n", - " \t\t\tname: HCT980X+Z110MB,\n", - " \t\t\tnamespace: https://w3id.org/steel/ProcessOntology\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 61c299bf-1434-4759-b290-fdadf9698451\n", - " \t\t}, {\n", - " \t\t\tid: 6d09ce07-1c41-4b3d-81e5-4be2ca04a7ad\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [{\n", - " \t\t\tname: YS2-Sz0-S3Q.xlsx\n", - " \t\t}],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: {'id': 'b0e5f8b6-192e-4713-96ca-6a023cf3af76', 'content': {'ExtensometerGaugeLength': 30.0, 'TestingMachine': 'Instron 5985', 'Material': 'HCT980X+Z110MB', 'ProjectName': 'crashartig', 'ProjectNumber': 100056.0, 'Radius': 2.0, 'ShearLength': 7.035, 'OriginalCrosssection': 10.65099, 'TestPieceGeometry': 'Shear Tensile Specimen', 'SampleIdentifier': 'YS2-Sz0°-S3Q', 'OriginalThickness': 1.514, 'CrossheadSeparationRate': 0.02}},\n", - " \t\t\tcreated_at: 2024-05-06T15:27:35.118023,\n", - " \t\t\tupdated_at: 2024-05-06T15:27:35.118023\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-06 16:53:59.576957, \n", - " \n", - " \tupdated_at = 2024-05-06 16:53:59.576957, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = e3487b03-e96a-49a4-bcb1-6599802ada8f, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo1-e3487b03, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f,\n", - " \t\t\tname: foo 2,\n", - " \t\t\tslug: foo2-3975cd66,\n", - " \t\t\tktype_id: organization,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:52:41.117372,\n", - " \t\t\tupdated_at: 2024-05-13T11:52:41.117372\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:52:40.683110, \n", - " \n", - " \tupdated_at = 2024-05-13 11:52:40.683110, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 2, \n", - " \n", - " \tid = 3975cd66-270a-4680-bfa6-07edd469295f, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo2-3975cd66, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f,\n", - " \t\t\tname: foo 1,\n", - " \t\t\tslug: foo1-e3487b03,\n", - " \t\t\tktype_id: dataset-catalog,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:52:40.683110,\n", - " \t\t\tupdated_at: 2024-05-13T11:52:40.683110\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:52:41.117372, \n", - " \n", - " \tupdated_at = 2024-05-13 11:52:41.117372, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 3, \n", - " \n", - " \tid = b1a05898-cf64-4dc2-85c1-ac9902cc48cf, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo3-b1a05898, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:52:41.529938, \n", - " \n", - " \tupdated_at = 2024-05-13 11:52:41.529938, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 4, \n", - " \n", - " \tid = 997d8c3d-5ebf-466b-9f9c-35fd13875260, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo4-997d8c3d, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/bar,\n", - " \t\t\tname: bar,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:52:41.967220, \n", - " \n", - " \tupdated_at = 2024-05-13 11:52:41.967220, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False)]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "... or for all of type `DatasetCatalog` with `foo` in the name:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = c60cca3e-1d14-4e8e-ad2d-92bda7f975e5, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo1-c60cca3e, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe,\n", - " \t\t\tname: foo 2,\n", - " \t\t\tslug: foo2-56791862,\n", - " \t\t\tktype_id: organization,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:49:54.440708,\n", - " \t\t\tupdated_at: 2024-05-13T11:49:54.440708\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:53.852559, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:53.852559, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 1, \n", - " \n", - " \tid = e3487b03-e96a-49a4-bcb1-6599802ada8f, \n", - " \n", - " \tktype_id = dataset-catalog, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo1-e3487b03, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f,\n", - " \t\t\tname: foo 2,\n", - " \t\t\tslug: foo2-3975cd66,\n", - " \t\t\tktype_id: organization,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", + " \t\t\n", + " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tname: foo 2\n", + " \t\t\tslug: foo2-de8f99cf\n", + " \t\t\tktype_id: organization\n", + " \t\t\tsummary: None\n", + " \t\t\tavatar_exists: False\n", " \t\t\tannotations: [{\n", " \t\t\tiri: www.example.org/foo,\n", " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:52:41.117372,\n", - " \t\t\tupdated_at: 2024-05-13T11:52:41.117372\n", - " \t\t}\n", + " \t\t\tnamespace: www.example.org,\n", + " \t\t\tdescription: None\n", + " \t\t}]\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t}]\n", + " \t\t\texternal_links: []\n", + " \t\t\tcontacts: []\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}]\n", + " \t\t\tlinked_affiliations: []\n", + " \t\t\tattachments: []\n", + " \t\t\tuser_groups: []\n", + " \t\t\tcustom_properties: None\n", + " \t\t\tcreated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\tupdated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\n", " \t], \n", " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", " \t\t}\n", " \t], \n", " \n", @@ -4135,9 +1233,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-05-13 11:52:40.683110, \n", + " \tcreated_at = 2024-08-01 15:17:21.860032, \n", " \n", - " \tupdated_at = 2024-05-13 11:52:40.683110, \n", + " \tupdated_at = 2024-08-01 15:17:21.860032, \n", " \n", " \texternal_links = [], \n", " \n", @@ -4149,7 +1247,7 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None, \n", + " \tdataframe = None, \n", " \n", " \trdf_exists = False\n", " ), fuzzy=False)]" @@ -4183,337 +1281,56 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = 56791862-dd99-4fdb-984a-f9741c7cfdfe, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo2-56791862, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/foo,\n", - " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: c60cca3e-1d14-4e8e-ad2d-92bda7f975e5,\n", - " \t\t\tname: foo 1,\n", - " \t\t\tslug: foo1-c60cca3e,\n", - " \t\t\tktype_id: dataset-catalog,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 56791862-dd99-4fdb-984a-f9741c7cfdfe\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:49:53.852559,\n", - " \t\t\tupdated_at: 2024-05-13T11:49:53.852559\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:54.440708, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:54.440708, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 2, \n", - " \n", - " \tid = 3975cd66-270a-4680-bfa6-07edd469295f, \n", + " \tid = de8f99cf-005e-4c5d-9710-c313ce12b55c, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo2-3975cd66, \n", + " \tslug = foo2-de8f99cf, \n", " \n", " \tannotations = [\n", " \t\t{\n", " \t\t\tiri: www.example.org/foo,\n", " \t\t\tname: foo,\n", - " \t\t\tnamespace: www.example.org\n", + " \t\t\tnamespace: www.example.org,\n", + " \t\t\tdescription: None\n", " \t\t}\n", " \t], \n", " \n", " \tattachments = [], \n", " \n", " \tlinked_kitems = [\n", - " \t\t{\n", - " \t\t\tid: e3487b03-e96a-49a4-bcb1-6599802ada8f,\n", - " \t\t\tname: foo 1,\n", - " \t\t\tslug: foo1-e3487b03,\n", - " \t\t\tktype_id: dataset-catalog,\n", - " \t\t\tsummary: None,\n", - " \t\t\tavatar_exists: False,\n", - " \t\t\tannotations: [],\n", - " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 3975cd66-270a-4680-bfa6-07edd469295f\n", - " \t\t}],\n", - " \t\t\texternal_links: [],\n", - " \t\t\tcontacts: [],\n", - " \t\t\tauthors: [{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}],\n", - " \t\t\tlinked_affiliations: [],\n", - " \t\t\tattachments: [],\n", - " \t\t\tuser_groups: [],\n", - " \t\t\tcustom_properties: None,\n", - " \t\t\tcreated_at: 2024-05-13T11:52:40.683110,\n", - " \t\t\tupdated_at: 2024-05-13T11:52:40.683110\n", - " \t\t}\n", - " \t], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:52:41.117372, \n", - " \n", - " \tupdated_at = 2024-05-13 11:52:41.117372, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=False),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 3, \n", - " \n", - " \tid = 4eb3de3b-0d7a-4d5e-b6f6-60c40a746802, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo3-4eb3de3b, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:54.988706, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:54.988706, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=284.02),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 3, \n", - " \n", - " \tid = b1a05898-cf64-4dc2-85c1-ac9902cc48cf, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo3-b1a05898, \n", - " \n", - " \tannotations = [], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:52:41.529938, \n", - " \n", - " \tupdated_at = 2024-05-13 11:52:41.529938, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=284.02),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 4, \n", - " \n", - " \tid = 9d541ea2-deb6-42e6-bd91-5778848e361e, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo4-9d541ea2, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/bar,\n", - " \t\t\tname: bar,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", - " \taffiliations = [], \n", - " \n", - " \tauthors = [\n", - " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", - " \t\t}\n", - " \t], \n", - " \n", - " \tavatar_exists = False, \n", - " \n", - " \tcontacts = [], \n", - " \n", - " \tcreated_at = 2024-05-13 11:49:55.546017, \n", - " \n", - " \tupdated_at = 2024-05-13 11:49:55.546017, \n", - " \n", - " \texternal_links = [], \n", - " \n", - " \tkitem_apps = [], \n", - " \n", - " \tsummary = None, \n", - " \n", - " \tuser_groups = [], \n", - " \n", - " \tcustom_properties = None, \n", - " \n", - " \thdf5 = None, \n", - " \n", - " \trdf_exists = False\n", - " ), fuzzy=284.24),\n", - " SearchResult(hit=KItem(\n", - " \n", - " \tname = foo 4, \n", - " \n", - " \tid = 997d8c3d-5ebf-466b-9f9c-35fd13875260, \n", - " \n", - " \tktype_id = organization, \n", - " \n", - " \tin_backend = True, \n", - " \n", - " \tslug = foo4-997d8c3d, \n", - " \n", - " \tannotations = [\n", - " \t\t{\n", - " \t\t\tiri: www.example.org/bar,\n", - " \t\t\tname: bar,\n", - " \t\t\tnamespace: www.example.org\n", - " \t\t}\n", + " \t\t\n", + " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tname: foo 1\n", + " \t\t\tslug: foo1-056dc8e1\n", + " \t\t\tktype_id: dataset-catalog\n", + " \t\t\tsummary: None\n", + " \t\t\tavatar_exists: False\n", + " \t\t\tannotations: []\n", + " \t\t\tlinked_kitems: [{\n", + " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t}]\n", + " \t\t\texternal_links: []\n", + " \t\t\tcontacts: []\n", + " \t\t\tauthors: [{\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}]\n", + " \t\t\tlinked_affiliations: []\n", + " \t\t\tattachments: []\n", + " \t\t\tuser_groups: []\n", + " \t\t\tcustom_properties: None\n", + " \t\t\tcreated_at: 2024-08-01T15:17:21.860032\n", + " \t\t\tupdated_at: 2024-08-01T15:17:21.860032\n", + " \t\t\n", " \t], \n", " \n", - " \tattachments = [], \n", - " \n", - " \tlinked_kitems = [], \n", - " \n", " \taffiliations = [], \n", " \n", " \tauthors = [\n", " \t\t{\n", - " \t\t\tuser_id: 29367f49-0562-43f9-b0cd-62b662446cc9\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", " \t\t}\n", " \t], \n", " \n", @@ -4521,9 +1338,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-05-13 11:52:41.967220, \n", + " \tcreated_at = 2024-08-01 15:17:22.446849, \n", " \n", - " \tupdated_at = 2024-05-13 11:52:41.967220, \n", + " \tupdated_at = 2024-08-01 15:17:22.446849, \n", " \n", " \texternal_links = [], \n", " \n", @@ -4535,10 +1352,10 @@ " \n", " \tcustom_properties = None, \n", " \n", - " \thdf5 = None, \n", + " \tdataframe = None, \n", " \n", " \trdf_exists = False\n", - " ), fuzzy=296.55)]" + " ), fuzzy=False)]" ] }, "execution_count": 21, @@ -4595,7 +1412,11 @@ { "data": { "text/plain": [ - "[App(filename='dsms-app-initiator/csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='dsms-app-initiator/csv_bulgetest'),\n", + "[App(filename='KupferDigital_import_csv_tensile_test/integrate_tensile_test_drag-drop.ipynb', basename='integrate_tensile_test_drag-drop.ipynb', folder='KupferDigital_import_csv_tensile_test'),\n", + " App(filename='KupferDigital_parameter_identification/material_card_export.ipynb', basename='material_card_export.ipynb', folder='KupferDigital_parameter_identification'),\n", + " App(filename='KupferDigital_parameter_identification/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='KupferDigital_parameter_identification'),\n", + " App(filename='ckan-fetch.argo.yaml', basename='ckan-fetch.argo.yaml', folder=''),\n", + " App(filename='dsms-app-initiator/csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='dsms-app-initiator/csv_bulgetest'),\n", " App(filename='dsms-app-initiator/csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='dsms-app-initiator/csv_tensile_test'),\n", " App(filename='dsms-app-initiator/csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='dsms-app-initiator/csv_tensile_test_f2'),\n", " App(filename='dsms-app-initiator/excel_component_test/excel_component_test.ipynb', basename='excel_component_test.ipynb', folder='dsms-app-initiator/excel_component_test'),\n", @@ -4603,7 +1424,10 @@ " App(filename='dsms-app-initiator/excel_notch_test/excel_notch_tensile_test.ipynb', basename='excel_notch_tensile_test.ipynb', folder='dsms-app-initiator/excel_notch_test'),\n", " App(filename='dsms-app-initiator/excel_shear_test/excel_shear_test.ipynb', basename='excel_shear_test.ipynb', folder='dsms-app-initiator/excel_shear_test'),\n", " App(filename='dsms-app-initiator/excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='dsms-app-initiator/excel_tensile_test'),\n", - " App(filename='graph_viz/graph_viz.ipynb', basename='graph_viz.ipynb', folder='graph_viz')]" + " App(filename='graph_viz/graph_viz.ipynb', basename='graph_viz.ipynb', folder='graph_viz'),\n", + " App(filename='tensile_test_bulk/csv_tensile_test_bulkupload.ipynb', basename='csv_tensile_test_bulkupload.ipynb', folder='tensile_test_bulk'),\n", + " App(filename='ternary-plot.argo.yaml', basename='ternary-plot.argo.yaml', folder=''),\n", + " App(filename='ternary_plot.ipynb', basename='ternary_plot.ipynb', folder='')]" ] }, "execution_count": 23, @@ -4631,7 +1455,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -4666,19 +1490,19 @@ "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", "\n", "\n", - "item = KItem(name=\"testdata1234\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", + "item = KItem(name=\"testdata1234\", ktype_id=dsms.ktypes.DatasetCatalog, dataframe=data)\n", "dsms.commit()\n", "\n", "print(\"Column-wise:\")\n", - "for column in item.hdf5:\n", + "for column in item.dataframe:\n", " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n", "\n", - "df = item.hdf5.to_df()\n", + "df = item.dataframe.to_df()\n", "print(\"\\nAs data frame:\")\n", "print(df)\n", "\n", "new_df = df.drop(['a'], axis=1)\n", - "item.hdf5 = new_df\n", + "item.dataframe = new_df\n", "\n", "dsms.commit()" ] diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb index 0ff0128..0d7844b 100644 --- a/examples/unit_conversion.ipynb +++ b/examples/unit_conversion.ipynb @@ -201,7 +201,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also directly convert the columns of a HDF5 dataframe of a dataframe into a unit of interest." + "We can also directly convert the columns of a dataframe into a unit of interest." ] }, { @@ -1234,10 +1234,10 @@ "uuid = \"8d6b034e-efb4-427a-a0d3-666e62762cba\"\n", "item = dsms[uuid]\n", "\n", - "print(\"Original unit:\", item.hdf5.get(\"Standardkraft\").get_unit())\n", + "print(\"Original unit:\", item.dataframe.get(\"Standardkraft\").get_unit())\n", "\n", "print(\"\\n\\nNow the converted data column in kN\")\n", - "item.hdf5.get(\"Standardkraft\").convert_to(\"kN\")\n" + "item.dataframe.get(\"Standardkraft\").convert_to(\"kN\")\n" ] } ], diff --git a/tests/conftest.py b/tests/conftest.py index 138e64b..e2f16d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,7 +40,7 @@ class MockDB: value.get("name") + "-" + key[:8] for key, value in kitems.items() ] - hdf5 = { + dataframe = { "698acdc5-dd97-4217-906e-2c0b44248c17": [ { "column_id": 0, @@ -114,17 +114,17 @@ def return_kitems(request): else: return 200, {}, json.dumps(MockDB.kitems[item_id]) - def return_hdf5(request): + def return_dataframe(request): # Extract 'id' parameter from the URL url_parts = request.url.split("/") item_id = url_parts[-1] # Your logic to generate a dynamic response based on 'item_id' # This is just a placeholder; you should replace it with your actual logic - if item_id not in MockDB.hdf5: + if item_id not in MockDB.dataframe: return 404, {}, "KItem does not exist" else: - return 200, {}, json.dumps(MockDB.hdf5[item_id]) + return 200, {}, json.dumps(MockDB.dataframe[item_id]) def return_slugs(request): url_parts = request.url.split("/") @@ -148,14 +148,14 @@ def _get_kitems() -> "Dict[str, Any]": for uid in MockDB.kitems } - def _get_hdf5() -> "Dict[str, Any]": + def _get_dataframe() -> "Dict[str, Any]": return { urljoin(custom_address, f"api/knowledge/data_api/{uid}"): [ { "method": responses.GET, "returns": { "content_type": "application/json", - "callback": return_hdf5, + "callback": return_dataframe, }, } ] @@ -189,7 +189,7 @@ def _get_slugs() -> "Dict[str, Any]": } ], **_get_kitems(), - **_get_hdf5(), + **_get_dataframe(), **_get_slugs(), } From 6a75006061e19ea9918732e601112489ded21ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 1 Aug 2024 17:26:07 +0200 Subject: [PATCH 093/114] Bump version v1.4.0rc20 -> v1.4.0rc21 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c4f4399..5a8af7e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc20 +version = v1.4.0rc21 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 5b988f53463f6e700510061633bdda8e1fe483fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Wed, 7 Aug 2024 18:09:16 +0200 Subject: [PATCH 094/114] update committing apps --- dsms/__init__.py | 3 +- dsms/apps/apps.py | 144 ++++++++++++++++++++++++++++++++++------ dsms/apps/utils.py | 20 ++++-- dsms/core/dsms.py | 4 +- dsms/knowledge/utils.py | 140 +++++++++++++++++++++++++++----------- 5 files changed, 248 insertions(+), 63 deletions(-) diff --git a/dsms/__init__.py b/dsms/__init__.py index 5d4f9b7..07422b0 100644 --- a/dsms/__init__.py +++ b/dsms/__init__.py @@ -1,9 +1,10 @@ """DSMS top module""" +from dsms.apps import App from dsms.core.configuration import Configuration from dsms.core.context import Context from dsms.core.dsms import DSMS from dsms.knowledge.kitem import KItem from dsms.knowledge.ktype import KType -__all__ = ["DSMS", "Configuration", "Context", "KItem", "KType"] +__all__ = ["DSMS", "Configuration", "Context", "KItem", "KType", "App"] diff --git a/dsms/apps/apps.py b/dsms/apps/apps.py index b29c607..09db0a9 100644 --- a/dsms/apps/apps.py +++ b/dsms/apps/apps.py @@ -1,34 +1,140 @@ """DSMS apps models""" +import logging import urllib.parse +from typing import TYPE_CHECKING, Optional -from pydantic import BaseModel, Field +from pydantic import ( # isort:skip + BaseModel, + ConfigDict, + Field, + field_validator, + model_validator, +) -from dsms.core.utils import _perform_request +from dsms.apps.utils import ( # isort:skip + _get_app_specification, + _get_available_apps, +) + + +from dsms.core.logging import handler # isort:skip + + +logger = logging.getLogger(__name__) +logger.addHandler(handler) +logger.propagate = False + +if TYPE_CHECKING: + from typing import Any + + from dsms import DSMS, Context class App(BaseModel): """KItem app list""" - filename: str = Field( - ..., description="File name of the notebook in the DSMS." + filename: Optional[str] = Field( + "", description="File name of the app in the DSMS." ) - basename: str = Field( - ..., description="Base name of the notebook in the DSMS." + basename: str = Field(..., description="Base name of the app in the DSMS.") + folder: Optional[str] = Field( + "", description="Directory of the app in the DSMS." ) - folder: str = Field( - ..., description="Directory of the notebook in the DSMS." + + specification: Optional[str] = Field( + None, + description="File path for content of YAML Specification of the app", + ) + + model_config = ConfigDict( + extra="forbid", + validate_assignment=True, + validate_default=True, + arbitrary_types_allowed=True, ) - def get(self, as_html: bool = False) -> str: - """Download the jupyter notebook""" - safe_filename = urllib.parse.quote_plus(self.filename) - response = _perform_request( - f"knowledge/api/apps/{safe_filename}", - "get", - params={"as_html": as_html}, + def __init__(self, **kwargs: "Any") -> None: + """Initialize the KItem""" + from dsms import DSMS + + logger.debug("Initialize KItem with model data: %s", kwargs) + + # set dsms instance if not already done + if not self.dsms: + self.dsms = DSMS() + + # initialize the app + super().__init__(**kwargs) + + # add app to buffer + if ( + not self.in_backend + and self.basename not in self.context.buffers.created + ): + logger.debug( + "Marking App with name `%s` as created and updated during App initialization.", + self.basename, + ) + self.context.buffers.created.update({self.basename: self}) + self.context.buffers.updated.update({self.basename: self}) + + logger.debug("App initialization successful.") + + def __setattr__(self, name, value) -> None: + """Add app to updated-buffer if an attribute is set""" + super().__setattr__(name, value) + logger.debug( + "Setting property with key `%s` on KItem level: %s.", name, value ) - if not response.ok: - message = f"Something went wrong downloading app `{self.filename}`: {response.text}" - raise RuntimeError(message) - return response.text + if self.basename not in self.context.buffers.updated: + logger.debug( + "Setting App with name `%s` as updated during App.__setattr__", + self.id, + ) + self.context.buffers.updated.update({self.basename: self}) + + @field_validator("basename") + @classmethod + def validate_basename(cls, value: str) -> str: + """Check whether the basename of the app contains invalid characters.""" + new_value = urllib.parse.quote_plus(value) + if not new_value == value: + raise ValueError(f"Basename contains invalid characters: {value}") + + @model_validator(mode="after") + @classmethod + def validate_app(cls, self: "App") -> "App": + """Validate app definition.""" + if self.in_backend and not self.specification: + self.specification = _get_app_specification(self.filename) + logger.info( + "App already exists in backend. Fetched the YAML specification." + ) + return self + + @property + def in_backend(self) -> bool: + """Checks whether the app already exists.""" + return self.basename in [ + app.get("basename") for app in _get_available_apps() + ] + + @property + def context(cls) -> "Context": + """Getter for Context""" + from dsms import ( # isort:skip + Context, + ) + + return Context + + @property + def dsms(self) -> "DSMS": + """DSMS context getter""" + return self.context.dsms + + @dsms.setter + def dsms(self, value: "DSMS") -> None: + """DSMS context setter""" + self.context.dsms = value diff --git a/dsms/apps/utils.py b/dsms/apps/utils.py index 8104ee0..795d259 100644 --- a/dsms/apps/utils.py +++ b/dsms/apps/utils.py @@ -1,19 +1,31 @@ """KItem Apps utils""" +import urllib.parse from typing import TYPE_CHECKING -from dsms.apps.apps import App from dsms.core.utils import _perform_request if TYPE_CHECKING: - from typing import List + from typing import Any, Dict, List -def _get_available_apps() -> "List[App]": +def _get_available_apps() -> "List[Dict[str, Any]]": """Get available KItem app.""" response = _perform_request("api/knowledge/apps", "get") if not response.ok: message = f"""Something went wrong fetching the available app list in the DSMS: {response.text}""" raise RuntimeError(message) - return [App(**app) for app in response.json()] + return response.json() + + +def _get_app_specification(appname) -> str: + safe_filename = urllib.parse.quote_plus(appname) + response = _perform_request( + f"knowledge/api/apps/{safe_filename}", + "get", + ) + if not response.ok: + message = f"Something went wrong downloading app `{appname}`: {response.text}" + raise RuntimeError(message) + return response.text diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index 2df8d86..26a30cc 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -182,7 +182,9 @@ def kitems(cls) -> "List[KItem]": @property def apps(cls) -> "List[App]": """Return available KItem apps in the DSMS""" - return _get_available_apps() + from dsms.apps import App + + return [App(**app) for app in _get_available_apps()] @property def buffers(cls) -> "Buffers": diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index a45bea1..5208be9 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -2,6 +2,7 @@ import base64 import io import logging +import os import re import warnings from enum import Enum @@ -33,6 +34,7 @@ from dsms.knowledge.search import SearchResult # isort:skip if TYPE_CHECKING: + from dsms.apps import App from dsms.core.context import Buffers from dsms.knowledge import KItem, KType @@ -492,55 +494,99 @@ def _commit(buffers: "Buffers") -> None: logger.debug("Committing successful, clearing buffers.") -def _commit_created(buffer: "Dict[str, KItem]") -> dict: +def _commit_created(buffer: "Dict[str, Union[KItem, KType, App]]") -> dict: """Commit the buffer for the `created` buffers""" - for kitem in buffer.values(): - _create_new_kitem(kitem) + from dsms import App, KItem, KType + + for obj in buffer.values(): + if isinstance(obj, KItem): + _create_new_kitem(obj) + elif isinstance(obj, App): + _create_or_update_app(obj) + elif isinstance(obj, KType): + raise NotImplementedError( + "Committing of KTypes not implemented yet." + ) + else: + raise TypeError( + f"Object `{obj}` of type {type(obj)} cannot be committed." + ) -def _commit_updated(buffer: "Dict[str, KItem]") -> None: +def _commit_updated(buffer: "Dict[str, Union[KItem, App, KType]]") -> None: """Commit the buffer for the `updated` buffers""" - for new_kitem in buffer.values(): - old_kitem = _get_kitem(new_kitem.id, as_json=True) + from dsms import App, KItem, KType + + for obj in buffer.values(): + if isinstance(obj, KItem): + _commit_updated_kitem(obj) + elif isinstance(obj, App): + _create_or_update_app(obj, overwrite=True) + elif isinstance(obj, KType): + raise NotImplementedError( + "Committing of KTypes not implemented yet." + ) + else: + raise TypeError( + f"Object `{obj}` of type {type(obj)} cannot be committed." + ) + + +def _commit_updated_kitem(new_kitem: "KItem") -> None: + """Commit the updated KItems""" + old_kitem = _get_kitem(new_kitem.id, as_json=True) + logger.debug( + "Fetched data from old KItem with id `%s`: %s", + new_kitem.id, + old_kitem, + ) + if old_kitem: + if isinstance(new_kitem.dataframe, pd.DataFrame): + logger.debug( + "New KItem data has `pd.DataFrame`. Will push as dataframe." + ) + _update_dataframe(new_kitem.id, new_kitem.dataframe) + new_kitem.dataframe = _inspect_dataframe(new_kitem.id) + elif isinstance( + new_kitem.dataframe, type(None) + ) and _inspect_dataframe(new_kitem.id): + _delete_dataframe(new_kitem.id) + _update_kitem(new_kitem, old_kitem) + _update_attachments(new_kitem, old_kitem) + if new_kitem.avatar.file or new_kitem.avatar.include_qr: + _commit_avatar(new_kitem) + new_kitem.in_backend = True logger.debug( - "Fetched data from old KItem with id `%s`: %s", - new_kitem.id, - old_kitem, + "Fetching updated KItem from remote backend: %s", new_kitem.id ) - if old_kitem: - if isinstance(new_kitem.dataframe, pd.DataFrame): - logger.debug( - "New KItem data has `pd.DataFrame`. Will push as dataframe." - ) - _update_dataframe(new_kitem.id, new_kitem.dataframe) - new_kitem.dataframe = _inspect_dataframe(new_kitem.id) - elif isinstance( - new_kitem.dataframe, type(None) - ) and _inspect_dataframe(new_kitem.id): - _delete_dataframe(new_kitem.id) - _update_kitem(new_kitem, old_kitem) - _update_attachments(new_kitem, old_kitem) - if new_kitem.avatar.file or new_kitem.avatar.include_qr: - _commit_avatar(new_kitem) - new_kitem.in_backend = True + for key, value in _get_kitem(new_kitem.id, as_json=True).items(): logger.debug( - "Fetching updated KItem from remote backend: %s", new_kitem.id + "Set updated property `%s` for KItem with id `%s` after commiting: %s", + key, + new_kitem.id, + value, ) - for key, value in _get_kitem(new_kitem.id, as_json=True).items(): - logger.debug( - "Set updated property `%s` for KItem with id `%s` after commiting: %s", - key, - new_kitem.id, - value, - ) - setattr(new_kitem, key, value) + setattr(new_kitem, key, value) -def _commit_deleted(buffer: "Dict[str, KItem]") -> None: +def _commit_deleted(buffer: "Dict[str, Union[KItem, KType, App]]") -> None: """Commit the buffer for the `deleted` buffers""" - for kitem in buffer.values(): - _delete_dataframe(kitem.id) - _delete_kitem(kitem) + from dsms import App, KItem, KType + + for obj in buffer.values(): + if isinstance(obj, KItem): + _delete_dataframe(obj.id) + _delete_kitem(obj) + elif isinstance(obj, App): + raise NotImplementedError("Deletion of Apps not implemented yet.") + elif isinstance(obj, KType): + raise NotImplementedError( + "Deletion of KTypes not implemented yet." + ) + else: + raise TypeError( + f"Object `{obj}` of type {type(obj)} cannot be committed or deleted." + ) def _split_iri(iri: str) -> List[str]: @@ -728,3 +774,21 @@ def _get_avatar(kitem: "KItem") -> Image.Image: response = _perform_request(f"api/knowledge/avatar/{kitem.id}", "get") buffer = io.BytesIO(response.content) return Image.open(buffer) + + +def _create_or_update_app(app: "App", overwrite=False) -> None: + if os.path.exists(app.specification): + with open(app.specification, mode="rb") as file: + upload_file = {"dataFile": file} + else: + upload_file = {"dataFile": io.StringIO(app.specification)} + response = _perform_request( + f"knowledge/api/apps/argo/{app.basename}", + "put", + files=upload_file, + params={"overwrite": overwrite}, + ) + if not response.ok: + message = f"Something went wrong uploading app with name `{app.basename}`: {response.text}" + raise RuntimeError(message) + return response.text From 3533133b7b8d634287e699223930c7fd1c63ddf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 8 Aug 2024 02:01:30 +0200 Subject: [PATCH 095/114] update app specification --- dsms/apps/apps.py | 76 +++++++++++++++---------------- dsms/apps/utils.py | 12 +++-- dsms/knowledge/properties/apps.py | 9 +++- dsms/knowledge/utils.py | 15 +++--- 4 files changed, 60 insertions(+), 52 deletions(-) diff --git a/dsms/apps/apps.py b/dsms/apps/apps.py index 09db0a9..dc11072 100644 --- a/dsms/apps/apps.py +++ b/dsms/apps/apps.py @@ -1,20 +1,20 @@ """DSMS apps models""" - import logging +import os import urllib.parse -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Dict, Union + +import yaml from pydantic import ( # isort:skip BaseModel, ConfigDict, Field, field_validator, - model_validator, ) from dsms.apps.utils import ( # isort:skip - _get_app_specification, - _get_available_apps, + _app_exists, ) @@ -26,31 +26,22 @@ logger.propagate = False if TYPE_CHECKING: - from typing import Any - from dsms import DSMS, Context class App(BaseModel): """KItem app list""" - filename: Optional[str] = Field( - "", description="File name of the app in the DSMS." - ) - basename: str = Field(..., description="Base name of the app in the DSMS.") - folder: Optional[str] = Field( - "", description="Directory of the app in the DSMS." - ) + name: str = Field(..., description="File name of the app in the DSMS.") - specification: Optional[str] = Field( - None, + specification: Union[str, Dict[str, Any]] = Field( + ..., description="File path for content of YAML Specification of the app", ) model_config = ConfigDict( extra="forbid", validate_assignment=True, - validate_default=True, arbitrary_types_allowed=True, ) @@ -70,14 +61,14 @@ def __init__(self, **kwargs: "Any") -> None: # add app to buffer if ( not self.in_backend - and self.basename not in self.context.buffers.created + and self.name not in self.context.buffers.created ): logger.debug( "Marking App with name `%s` as created and updated during App initialization.", - self.basename, + self.name, ) - self.context.buffers.created.update({self.basename: self}) - self.context.buffers.updated.update({self.basename: self}) + self.context.buffers.created.update({self.name: self}) + self.context.buffers.updated.update({self.name: self}) logger.debug("App initialization successful.") @@ -87,38 +78,47 @@ def __setattr__(self, name, value) -> None: logger.debug( "Setting property with key `%s` on KItem level: %s.", name, value ) - if self.basename not in self.context.buffers.updated: + if self.name not in self.context.buffers.updated: logger.debug( "Setting App with name `%s` as updated during App.__setattr__", self.id, ) - self.context.buffers.updated.update({self.basename: self}) + self.context.buffers.updated.update({self.name: self}) - @field_validator("basename") + @field_validator("name") @classmethod - def validate_basename(cls, value: str) -> str: - """Check whether the basename of the app contains invalid characters.""" + def validate_name(cls, value: str) -> str: + """Check whether the name of the app contains invalid characters.""" new_value = urllib.parse.quote_plus(value) if not new_value == value: raise ValueError(f"Basename contains invalid characters: {value}") + return value - @model_validator(mode="after") + @field_validator("specification") @classmethod - def validate_app(cls, self: "App") -> "App": - """Validate app definition.""" - if self.in_backend and not self.specification: - self.specification = _get_app_specification(self.filename) - logger.info( - "App already exists in backend. Fetched the YAML specification." - ) - return self + def validate_specification(cls, value: Union[str, Dict[str, Any]]) -> str: + """Check whether the specification to be uploaded""" + from dsms import Context + + if isinstance(value, str): + try: + if os.path.exists(value): + with open( + value, encoding=Context.dsms.config.encoding + ) as file: + value = yaml.safe_load(file.read()) + else: + value = yaml.safe_load(value) + except Exception as error: + raise RuntimeError( + "Invalid file path or YAML syntax." + ) from error + return value @property def in_backend(self) -> bool: """Checks whether the app already exists.""" - return self.basename in [ - app.get("basename") for app in _get_available_apps() - ] + return _app_exists(self.name) @property def context(cls) -> "Context": diff --git a/dsms/apps/utils.py b/dsms/apps/utils.py index 795d259..4aad5f1 100644 --- a/dsms/apps/utils.py +++ b/dsms/apps/utils.py @@ -1,6 +1,5 @@ """KItem Apps utils""" -import urllib.parse from typing import TYPE_CHECKING from dsms.core.utils import _perform_request @@ -11,7 +10,7 @@ def _get_available_apps() -> "List[Dict[str, Any]]": """Get available KItem app.""" - response = _perform_request("api/knowledge/apps", "get") + response = _perform_request("api/knowledge/apps/list/argo", "get") if not response.ok: message = f"""Something went wrong fetching the available app list in the DSMS: {response.text}""" @@ -19,10 +18,15 @@ def _get_available_apps() -> "List[Dict[str, Any]]": return response.json() +def _app_exists(name: str) -> bool: + """Check whether the specification of the app already exists.""" + response = _perform_request(f"api/knowledge/apps/argo/{name}", "head") + return response.ok + + def _get_app_specification(appname) -> str: - safe_filename = urllib.parse.quote_plus(appname) response = _perform_request( - f"knowledge/api/apps/{safe_filename}", + f"knowledge/api/apps/argo/{appname}", "get", ) if not response.ok: diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 0124775..8df0f35 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -87,7 +87,7 @@ def serialize_author(self) -> Dict[str, Any]: if key not in ["id", "kitem_app_id"] } - def run(self, wait=True, **kwargs) -> None: + def run(self, wait=True, token=False, **kwargs) -> None: """Run application. Args: @@ -95,11 +95,18 @@ def run(self, wait=True, **kwargs) -> None: (not in the background), but the object should wait until the job finished. Warning: this may lead to a request timeout for long running jobs! Job details may not be associated anymore when this occurs. + token (bool, optional): Whether the job also should receive the access + token as paramter.If `True`, the JWT will be set as parameter with + the name ´access_token`. **kwargs (Any, optional): Additional arguments to be passed to the workflow. KItem ID is passed automatically """ kwargs["kitem_id"] = str(self.id) + if token: + kwargs[ + "access_token" + ] = self.context.dsms.config.token.get_secret_value() if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member name = self.executable.strip( # pylint: disable=no-member diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 5208be9..3a3a3b1 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -2,7 +2,6 @@ import base64 import io import logging -import os import re import warnings from enum import Enum @@ -12,6 +11,7 @@ import pandas as pd import segno +import yaml from PIL import Image from requests import Response @@ -777,18 +777,15 @@ def _get_avatar(kitem: "KItem") -> Image.Image: def _create_or_update_app(app: "App", overwrite=False) -> None: - if os.path.exists(app.specification): - with open(app.specification, mode="rb") as file: - upload_file = {"dataFile": file} - else: - upload_file = {"dataFile": io.StringIO(app.specification)} + """Create app specfication""" + upload_file = {"def_file": io.StringIO(yaml.safe_dump(app.specification))} response = _perform_request( - f"knowledge/api/apps/argo/{app.basename}", - "put", + f"/api/knowledge/apps/argo/{app.name}", + "post", files=upload_file, params={"overwrite": overwrite}, ) if not response.ok: - message = f"Something went wrong uploading app with name `{app.basename}`: {response.text}" + message = f"Something went wrong uploading app with name `{app.name}`: {response.text}" raise RuntimeError(message) return response.text From 89f71b139b592553df929521fd85f62ac4782276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Thu, 8 Aug 2024 17:04:55 +0200 Subject: [PATCH 096/114] update submission of app specs, add job logs, debug waiting for job results, minor refactor of AppConfig model --- dsms/__init__.py | 4 +- dsms/apps/__init__.py | 4 +- dsms/apps/{apps.py => config.py} | 50 +++++++++++---------- dsms/apps/utils.py | 4 +- dsms/core/configuration.py | 2 +- dsms/core/dsms.py | 4 +- dsms/knowledge/properties/apps.py | 72 ++++++++++++++++--------------- dsms/knowledge/utils.py | 36 ++++++++++------ examples/create_and_run_app.py | 69 +++++++++++++++++++++++++++++ tests/test_utils.py | 8 ++-- 10 files changed, 168 insertions(+), 85 deletions(-) rename dsms/apps/{apps.py => config.py} (69%) create mode 100644 examples/create_and_run_app.py diff --git a/dsms/__init__.py b/dsms/__init__.py index 07422b0..1ab4066 100644 --- a/dsms/__init__.py +++ b/dsms/__init__.py @@ -1,10 +1,10 @@ """DSMS top module""" -from dsms.apps import App +from dsms.apps import AppConfig from dsms.core.configuration import Configuration from dsms.core.context import Context from dsms.core.dsms import DSMS from dsms.knowledge.kitem import KItem from dsms.knowledge.ktype import KType -__all__ = ["DSMS", "Configuration", "Context", "KItem", "KType", "App"] +__all__ = ["DSMS", "Configuration", "Context", "KItem", "KType", "AppConfig"] diff --git a/dsms/apps/__init__.py b/dsms/apps/__init__.py index 6c22c46..fabda28 100644 --- a/dsms/apps/__init__.py +++ b/dsms/apps/__init__.py @@ -1,5 +1,5 @@ """DSMS apps""" -from .apps import App +from .config import AppConfig -__all__ = ["App"] +__all__ = ["AppConfig"] diff --git a/dsms/apps/apps.py b/dsms/apps/config.py similarity index 69% rename from dsms/apps/apps.py rename to dsms/apps/config.py index dc11072..af2b433 100644 --- a/dsms/apps/apps.py +++ b/dsms/apps/config.py @@ -1,6 +1,5 @@ """DSMS apps models""" import logging -import os import urllib.parse from typing import TYPE_CHECKING, Any, Dict, Union @@ -11,10 +10,11 @@ ConfigDict, Field, field_validator, + model_validator, ) from dsms.apps.utils import ( # isort:skip - _app_exists, + _app_spec_exists, ) @@ -29,14 +29,14 @@ from dsms import DSMS, Context -class App(BaseModel): +class AppConfig(BaseModel): """KItem app list""" name: str = Field(..., description="File name of the app in the DSMS.") specification: Union[str, Dict[str, Any]] = Field( ..., - description="File path for content of YAML Specification of the app", + description="File path for YAML Specification of the app", ) model_config = ConfigDict( @@ -64,13 +64,14 @@ def __init__(self, **kwargs: "Any") -> None: and self.name not in self.context.buffers.created ): logger.debug( - "Marking App with name `%s` as created and updated during App initialization.", + """Marking AppConfig with name `%s` as created + and updated during AppConfig initialization.""", self.name, ) self.context.buffers.created.update({self.name: self}) self.context.buffers.updated.update({self.name: self}) - logger.debug("App initialization successful.") + logger.debug("AppConfig initialization successful.") def __setattr__(self, name, value) -> None: """Add app to updated-buffer if an attribute is set""" @@ -80,8 +81,8 @@ def __setattr__(self, name, value) -> None: ) if self.name not in self.context.buffers.updated: logger.debug( - "Setting App with name `%s` as updated during App.__setattr__", - self.id, + "Setting AppConfig with name `%s` as updated during AppConfig.__setattr__", + self.name, ) self.context.buffers.updated.update({self.name: self}) @@ -94,31 +95,34 @@ def validate_name(cls, value: str) -> str: raise ValueError(f"Basename contains invalid characters: {value}") return value - @field_validator("specification") + @model_validator(mode="after") @classmethod - def validate_specification(cls, value: Union[str, Dict[str, Any]]) -> str: - """Check whether the specification to be uploaded""" - from dsms import Context + def validate_specification(cls, self: "AppConfig") -> str: + """Check specification to be uploaded""" - if isinstance(value, str): + if isinstance(self.specification, str): + try: + with open( + self.specification, encoding=self.dsms.config.encoding + ) as file: + content = file.read() + except Exception as error: + raise FileNotFoundError( + f"Invalid file path. File does not exist under path `{self.specification}`." + ) from error try: - if os.path.exists(value): - with open( - value, encoding=Context.dsms.config.encoding - ) as file: - value = yaml.safe_load(file.read()) - else: - value = yaml.safe_load(value) + self.specification = yaml.safe_load(content) except Exception as error: raise RuntimeError( - "Invalid file path or YAML syntax." + f"Invalid yaml specification path: `{error.args[0]}`" ) from error - return value + self.context.buffers.updated.update({self.name: self}) + return self @property def in_backend(self) -> bool: """Checks whether the app already exists.""" - return _app_exists(self.name) + return _app_spec_exists(self.name) @property def context(cls) -> "Context": diff --git a/dsms/apps/utils.py b/dsms/apps/utils.py index 4aad5f1..dbcb809 100644 --- a/dsms/apps/utils.py +++ b/dsms/apps/utils.py @@ -8,7 +8,7 @@ from typing import Any, Dict, List -def _get_available_apps() -> "List[Dict[str, Any]]": +def _get_available_apps_specs() -> "List[Dict[str, Any]]": """Get available KItem app.""" response = _perform_request("api/knowledge/apps/list/argo", "get") if not response.ok: @@ -18,7 +18,7 @@ def _get_available_apps() -> "List[Dict[str, Any]]": return response.json() -def _app_exists(name: str) -> bool: +def _app_spec_exists(name: str) -> bool: """Check whether the specification of the app already exists.""" response = _perform_request(f"api/knowledge/apps/argo/{name}", "head") return response.ok diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index cc753b9..9237366 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -35,7 +35,7 @@ class Configuration(BaseSettings): ..., description="Url of the DSMS instance to connect." ) request_timeout: int = Field( - 30, + 120, description="Timeout in seconds until the request to the DSMS is timed out.", ) diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index 26a30cc..fd6d04d 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -5,7 +5,7 @@ from dotenv import load_dotenv -from dsms.apps.utils import _get_available_apps +from dsms.apps.utils import _get_available_apps_specs from dsms.core.configuration import Configuration from dsms.core.context import Context from dsms.core.utils import _ping_dsms @@ -184,7 +184,7 @@ def apps(cls) -> "List[App]": """Return available KItem apps in the DSMS""" from dsms.apps import App - return [App(**app) for app in _get_available_apps()] + return [App(**app) for app in _get_available_apps_specs()] @property def buffers(cls) -> "Buffers": diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 8df0f35..324b254 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -66,7 +66,7 @@ class App(KItemProperty): executable: str = Field( ..., description="Name of the executable related to the app" ) - title: Optional[str] = Field(None, description="Title of the appilcation") + title: str = Field(..., description="Title of the appilcation") description: Optional[str] = Field( None, description="Description of the appilcation" ) @@ -87,7 +87,13 @@ def serialize_author(self) -> Dict[str, Any]: if key not in ["id", "kitem_app_id"] } - def run(self, wait=True, token=False, **kwargs) -> None: + def run( + self, + wait=True, + set_token: bool = False, + set_host_url: bool = False, + **kwargs, + ) -> None: """Run application. Args: @@ -103,40 +109,31 @@ def run(self, wait=True, token=False, **kwargs) -> None: """ kwargs["kitem_id"] = str(self.id) - if token: + if set_token: kwargs[ "access_token" ] = self.context.dsms.config.token.get_secret_value() - - if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member - name = self.executable.strip( # pylint: disable=no-member - ".argo.yaml" - ) - response = _perform_request( - f"api/knowledge/apps/argo/job/{name}", - "post", - json=kwargs, - params={"wait": wait}, + if set_host_url: + kwargs["host_url"] = str(self.context.dsms.config.host_url) + + response = _perform_request( + f"api/knowledge/apps/argo/job/{self.executable}", + "post", + json=kwargs, + params={"wait": wait}, + ) + if not response.ok: + raise RuntimeError( + f"Submission was not successful: {response.text}" ) - if not response.ok: - raise RuntimeError( - f"Submission was not successful: {response.text}" - ) - submitted = response.json() - else: - raise TypeError("Type of app not supported yet.") + submitted = response.json() + return Job(name=submitted.get("name"), executable=self.executable) @property def inputs(self) -> Dict[str, Any]: """Inputs defined for the app from the webform builder""" - if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member - name = self.executable.strip( # pylint: disable=no-member - ".argo.yaml" - ) - route = f"api/knowledge/apps/argo/{name}/inputs" - else: - raise TypeError("Inputs for type of app not supported yet.") + route = f"api/knowledge/apps/argo/{self.executable}/inputs" response = _perform_request(route, "get") if not response.ok: raise RuntimeError( @@ -157,10 +154,9 @@ class Job(BaseModel): @property def status(self) -> JobStatus: """Get the status of the currently running job""" - if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member - route = f"api/knowledge/apps/argo/job/{self.name}/status" - else: - raise TypeError("Status for type of app not supported yet.") + + route = f"api/knowledge/apps/argo/job/{self.name}/status" + response = _perform_request(route, "get") if not response.ok: raise RuntimeError(f"Could not fetch job status: {response.text}") @@ -170,10 +166,7 @@ def status(self) -> JobStatus: def artifacts(self) -> Dict[str, Any]: """Get the atrifcats of a finished job""" - if self.executable.endswith(".argo.yaml"): # pylint: disable=no-member - route = f"api/knowledge/apps/argo/job/{self.name}/artifacts" - else: - raise TypeError("Artifacts for type of app not supported yet.") + route = f"api/knowledge/apps/argo/job/{self.name}/artifacts" response = _perform_request(route, "get") if not response.ok: raise RuntimeError( @@ -181,6 +174,15 @@ def artifacts(self) -> Dict[str, Any]: ) return response.json() + @property + def logs(self) -> str: + """Get the logs of a job""" + route = f"api/knowledge/apps/argo/job/{self.name}/logs" + response = _perform_request(route, "get") + if not response.ok: + raise RuntimeError(f"Could not fetch job logs: {response.text}") + return response.text + class AppsProperty(KItemPropertyList): """KItemPropertyList for apps""" diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 3a3a3b1..1cf8519 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -34,7 +34,7 @@ from dsms.knowledge.search import SearchResult # isort:skip if TYPE_CHECKING: - from dsms.apps import App + from dsms.apps import AppConfig from dsms.core.context import Buffers from dsms.knowledge import KItem, KType @@ -494,15 +494,17 @@ def _commit(buffers: "Buffers") -> None: logger.debug("Committing successful, clearing buffers.") -def _commit_created(buffer: "Dict[str, Union[KItem, KType, App]]") -> dict: +def _commit_created( + buffer: "Dict[str, Union[KItem, KType, AppConfig]]", +) -> dict: """Commit the buffer for the `created` buffers""" - from dsms import App, KItem, KType + from dsms import AppConfig, KItem, KType for obj in buffer.values(): if isinstance(obj, KItem): _create_new_kitem(obj) - elif isinstance(obj, App): - _create_or_update_app(obj) + elif isinstance(obj, AppConfig): + _create_or_update_app_spec(obj) elif isinstance(obj, KType): raise NotImplementedError( "Committing of KTypes not implemented yet." @@ -513,15 +515,17 @@ def _commit_created(buffer: "Dict[str, Union[KItem, KType, App]]") -> dict: ) -def _commit_updated(buffer: "Dict[str, Union[KItem, App, KType]]") -> None: +def _commit_updated( + buffer: "Dict[str, Union[KItem, AppConfig, KType]]", +) -> None: """Commit the buffer for the `updated` buffers""" - from dsms import App, KItem, KType + from dsms import AppConfig, KItem, KType for obj in buffer.values(): if isinstance(obj, KItem): _commit_updated_kitem(obj) - elif isinstance(obj, App): - _create_or_update_app(obj, overwrite=True) + elif isinstance(obj, AppConfig): + _create_or_update_app_spec(obj, overwrite=True) elif isinstance(obj, KType): raise NotImplementedError( "Committing of KTypes not implemented yet." @@ -569,16 +573,20 @@ def _commit_updated_kitem(new_kitem: "KItem") -> None: setattr(new_kitem, key, value) -def _commit_deleted(buffer: "Dict[str, Union[KItem, KType, App]]") -> None: +def _commit_deleted( + buffer: "Dict[str, Union[KItem, KType, AppConfig]]", +) -> None: """Commit the buffer for the `deleted` buffers""" - from dsms import App, KItem, KType + from dsms import AppConfig, KItem, KType for obj in buffer.values(): if isinstance(obj, KItem): _delete_dataframe(obj.id) _delete_kitem(obj) - elif isinstance(obj, App): - raise NotImplementedError("Deletion of Apps not implemented yet.") + elif isinstance(obj, AppConfig): + raise NotImplementedError( + "Deletion of AppConfigs not implemented yet." + ) elif isinstance(obj, KType): raise NotImplementedError( "Deletion of KTypes not implemented yet." @@ -776,7 +784,7 @@ def _get_avatar(kitem: "KItem") -> Image.Image: return Image.open(buffer) -def _create_or_update_app(app: "App", overwrite=False) -> None: +def _create_or_update_app_spec(app: "AppConfig", overwrite=False) -> None: """Create app specfication""" upload_file = {"def_file": io.StringIO(yaml.safe_dump(app.specification))} response = _perform_request( diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py new file mode 100644 index 0000000..4c85492 --- /dev/null +++ b/examples/create_and_run_app.py @@ -0,0 +1,69 @@ +"""Example of creating and running apps""" + +import time + +from dsms import DSMS, AppConfig, KItem + +print("\nConnect to DSMS") +dsms = DSMS(env="../.env") + +print("\nCreate app specification") +AppConfig( + name="testapp", + specification="../../dsms-app-initiator/excel_tensile_test.argo.yaml", +) + +print("\nCreate kitem") +item = KItem( + name="test123", + ktype_id=dsms.ktypes.Dataset, + kitem_apps=[ + { + "executable": "testapp", + "title": "data2rdf", + # "additional_properties": { + # "triggerUponUpload": True, + # "triggerUponUploadFileExtensions": [".xlsx"], + # }, + } + ], +) +print("\nCommit KItem") +dsms.commit() + +print("\nAdd attachment") +item.attachments = ["../../Desktop/AFZ1-Fz-S1D.xlsx"] + + +print("\nUpload attachment and trigger app") +dsms.commit() + +print(item) + +print("\nRun pipeline manually") +job = item.kitem_apps.by_title["data2rdf"].run( + attachment_name=item.attachments[0].name, set_token=True, set_host_url=True +) + +print("\nSee job status") +print(job.status) +print("\n See job logs:") +print(job.logs) + +print("\nRun pipeline manually in the background") +job = item.kitem_apps.by_title["data2rdf"].run( + attachment_name=item.attachments[0].name, + set_token=True, + set_host_url=True, + wait=False, +) + +print("\nMonitor job status") +while True: + time.sleep(1) + print("\n Current status:") + print(job.status) + print("\n Current logs:") + print(job.logs) + if job.status.phase == "Succeeded": + break diff --git a/tests/test_utils.py b/tests/test_utils.py index 2ec04a8..485c474 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -52,7 +52,7 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): ) user_group = {"name": "private", "group_id": "private_123"} - app = {"executable": "foo.exe"} + app = {"executable": "foo.exe", "title": "foo"} kitem_old = { "id": get_mock_kitem_ids[0], @@ -84,7 +84,7 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): "id": get_mock_kitem_ids[0], "kitem_app_id": 17, "executable": "bar.exe", - "title": None, + "title": "bar", "description": None, "tags": None, "additional_properties": None, @@ -124,7 +124,7 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): "kitem_apps_to_update": [ { "executable": "foo.exe", - "title": None, + "title": "foo", "description": None, "tags": None, "additional_properties": None, @@ -145,7 +145,7 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): "kitem_apps_to_remove": [ { "executable": "bar.exe", - "title": None, + "title": "bar", "description": None, "tags": None, "additional_properties": None, From a551419905bcb3ce4c17acf454923f5e263ecb34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 9 Aug 2024 00:40:35 +0200 Subject: [PATCH 097/114] update app config api requesting, add attachment by content --- dsms/apps/config.py | 35 ++++++++++++++++++--- dsms/apps/utils.py | 12 +++---- dsms/core/dsms.py | 15 +++++---- dsms/knowledge/properties/attachments.py | 40 +++++++++++++++++++++--- dsms/knowledge/utils.py | 36 ++++++++++++++++++--- examples/basic_usage.ipynb | 36 +++++++-------------- examples/create_and_run_app.py | 10 +++--- 7 files changed, 128 insertions(+), 56 deletions(-) diff --git a/dsms/apps/config.py b/dsms/apps/config.py index af2b433..17b9ec5 100644 --- a/dsms/apps/config.py +++ b/dsms/apps/config.py @@ -1,4 +1,4 @@ -"""DSMS apps models""" +"""DSMS app models""" import logging import urllib.parse from typing import TYPE_CHECKING, Any, Dict, Union @@ -30,7 +30,7 @@ class AppConfig(BaseModel): - """KItem app list""" + """App config model""" name: str = Field(..., description="File name of the app in the DSMS.") @@ -55,10 +55,10 @@ def __init__(self, **kwargs: "Any") -> None: if not self.dsms: self.dsms = DSMS() - # initialize the app + # initialize the app config super().__init__(**kwargs) - # add app to buffer + # add app config to buffer if ( not self.in_backend and self.name not in self.context.buffers.created @@ -86,6 +86,31 @@ def __setattr__(self, name, value) -> None: ) self.context.buffers.updated.update({self.name: self}) + def __str__(self) -> str: + """Pretty print the app config fields""" + fields = ", ".join( + [ + "{key}={value}".format( # pylint: disable=consider-using-f-string + key=key, + value=( + value + if key != "specification" + else { + "metadata": value.get( # pylint: disable=no-member + "metadata" + ) + } + ), + ) + for key, value in self.__dict__.items() + ] + ) + return f"{self.__class__.__name__}({fields})" + + def __repr__(self) -> str: + """Pretty print the kitem Fields""" + return str(self) + @field_validator("name") @classmethod def validate_name(cls, value: str) -> str: @@ -121,7 +146,7 @@ def validate_specification(cls, self: "AppConfig") -> str: @property def in_backend(self) -> bool: - """Checks whether the app already exists.""" + """Checks whether the app config already exists.""" return _app_spec_exists(self.name) @property diff --git a/dsms/apps/utils.py b/dsms/apps/utils.py index dbcb809..a5151fa 100644 --- a/dsms/apps/utils.py +++ b/dsms/apps/utils.py @@ -9,10 +9,10 @@ def _get_available_apps_specs() -> "List[Dict[str, Any]]": - """Get available KItem app.""" - response = _perform_request("api/knowledge/apps/list/argo", "get") + """Get available KItem app specs.""" + response = _perform_request("api/knowledge/apps/argo/list", "get") if not response.ok: - message = f"""Something went wrong fetching the available app + message = f"""Something went wrong fetching the available app configs list in the DSMS: {response.text}""" raise RuntimeError(message) return response.json() @@ -20,16 +20,16 @@ def _get_available_apps_specs() -> "List[Dict[str, Any]]": def _app_spec_exists(name: str) -> bool: """Check whether the specification of the app already exists.""" - response = _perform_request(f"api/knowledge/apps/argo/{name}", "head") + response = _perform_request(f"api/knowledge/apps/argo/spec/{name}", "head") return response.ok def _get_app_specification(appname) -> str: response = _perform_request( - f"knowledge/api/apps/argo/{appname}", + f"knowledge/api/apps/argo/spec/{appname}", "get", ) if not response.ok: - message = f"Something went wrong downloading app `{appname}`: {response.text}" + message = f"Something went wrong downloading app config `{appname}`: {response.text}" raise RuntimeError(message) return response.text diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index fd6d04d..d71b003 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -23,7 +23,7 @@ from enum import Enum from typing import Optional - from dsms.apps import App + from dsms.apps import AppConfig from dsms.core.context import Buffers from dsms.knowledge.kitem import KItem from dsms.knowledge.ktype import KType @@ -180,11 +180,14 @@ def kitems(cls) -> "List[KItem]": return _get_kitem_list() @property - def apps(cls) -> "List[App]": - """Return available KItem apps in the DSMS""" - from dsms.apps import App - - return [App(**app) for app in _get_available_apps_specs()] + def app_configs(cls) -> "List[AppConfig]": + """Return available app configs in the DSMS""" + from dsms.apps import AppConfig + + return [ + AppConfig(**app_config) + for app_config in _get_available_apps_specs() + ] @property def buffers(cls) -> "Buffers": diff --git a/dsms/knowledge/properties/attachments.py b/dsms/knowledge/properties/attachments.py index c1d4978..10df1e7 100644 --- a/dsms/knowledge/properties/attachments.py +++ b/dsms/knowledge/properties/attachments.py @@ -1,16 +1,16 @@ """Attachment property of a KItem""" from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Union -from pydantic import Field +from pydantic import ConfigDict, Field from dsms.knowledge.properties.base import KItemProperty, KItemPropertyList from dsms.knowledge.properties.utils import _str_to_dict from dsms.knowledge.utils import _get_attachment if TYPE_CHECKING: - from typing import Any, Callable, Dict, Iterable, List, Union + from typing import Any, Callable, Dict, Iterable, List class Attachment(KItemProperty): @@ -18,9 +18,41 @@ class Attachment(KItemProperty): name: str = Field(..., description="File name of the attachment") + content: Optional[Union[str, bytes]] = Field( + None, description="Content of the file" + ) + + # OVERRIDE + model_config = ConfigDict( + exclude={"id", "content"}, + populate_by_name=True, + from_attributes=True, + ) + + # OVERRIDE + def __str__(self) -> str: + """Pretty print the Attachment""" + values = ",\n\t\t\t".join( + [ + f"{key}: {value}" + for key, value in self.__dict__.items() + if key not in self.exclude + ] + ) + return f"{{\n\t\t\t{values}\n\t\t}}" + + # OVERRIDE + def __repr__(self) -> str: + """Pretty print the Attachment""" + return str(self) + def download(self, as_bytes: bool = False) -> "Union[str, bytes]": """Download attachment file""" - return _get_attachment(self.id, self.name, as_bytes) + if not self.content: + content = _get_attachment(self.id, self.name, as_bytes) + else: + content = self.content + return content class AttachmentsProperty(KItemPropertyList): diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 1cf8519..5769cfc 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -37,6 +37,7 @@ from dsms.apps import AppConfig from dsms.core.context import Buffers from dsms.knowledge import KItem, KType + from dsms.knowledge.properties import Attachment logger = logging.getLogger(__name__) logger.addHandler(handler) @@ -342,11 +343,11 @@ def _update_attachments( _delete_attachments(new_kitem, remove) -def _upload_attachments(kitem: "KItem", attachment: "str") -> None: +def _upload_attachments(kitem: "KItem", attachment: "Attachment") -> None: """Upload the attachments of the KItem""" - path = Path(attachment) + path = Path(attachment.name) - if path.is_file(): + if path.is_file() and not attachment.content: if not path.exists(): raise FileNotFoundError(f"File {path} does not exist.") @@ -361,6 +362,31 @@ def _upload_attachments(kitem: "KItem", attachment: "str") -> None: raise RuntimeError( f"Could not upload attachment `{path}`: {response.text}" ) + elif not path.is_file() and attachment.content: + if isinstance(attachment.content, str): + file = io.StringIO(attachment.content) + elif isinstance(attachment.content, bytes): + file = io.BytesIO(attachment.content) + else: + raise TypeError( + f"""Invalid content type of attachment with name + `{attachment.name}`: {type(attachment.content)}""" + ) + upload_file = {"dataFile": file} + response = _perform_request( + f"api/knowledge/attachments/{kitem.id}", + "put", + files=upload_file, + ) + if not response.ok: + raise RuntimeError( + f"Could not upload attachment `{path}`: {response.text}" + ) + else: + raise RuntimeError( + f"""Invalid file path, attachment name or attachment content: + name={attachment.name},content={attachment.content}""" + ) def _delete_attachments(kitem: "KItem", file_name: str) -> None: @@ -442,9 +468,9 @@ def _get_attachment_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): if attachment not in kitem_new.attachments.by_name ], "add": [ - attachment.name + attachment for name, attachment in kitem_new.attachments.by_name.items() - if name not in old_attachments + if name not in old_attachments or attachment.content ], } diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 65a64d6..52701f2 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -1406,37 +1406,23 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 1, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "[App(filename='KupferDigital_import_csv_tensile_test/integrate_tensile_test_drag-drop.ipynb', basename='integrate_tensile_test_drag-drop.ipynb', folder='KupferDigital_import_csv_tensile_test'),\n", - " App(filename='KupferDigital_parameter_identification/material_card_export.ipynb', basename='material_card_export.ipynb', folder='KupferDigital_parameter_identification'),\n", - " App(filename='KupferDigital_parameter_identification/tensiletest_parameter_identification.ipynb', basename='tensiletest_parameter_identification.ipynb', folder='KupferDigital_parameter_identification'),\n", - " App(filename='ckan-fetch.argo.yaml', basename='ckan-fetch.argo.yaml', folder=''),\n", - " App(filename='dsms-app-initiator/csv_bulgetest/csv_bulgetest.ipynb', basename='csv_bulgetest.ipynb', folder='dsms-app-initiator/csv_bulgetest'),\n", - " App(filename='dsms-app-initiator/csv_tensile_test/csv_tensile_test.ipynb', basename='csv_tensile_test.ipynb', folder='dsms-app-initiator/csv_tensile_test'),\n", - " App(filename='dsms-app-initiator/csv_tensile_test_f2/csv_tensile_test_f2.ipynb', basename='csv_tensile_test_f2.ipynb', folder='dsms-app-initiator/csv_tensile_test_f2'),\n", - " App(filename='dsms-app-initiator/excel_component_test/excel_component_test.ipynb', basename='excel_component_test.ipynb', folder='dsms-app-initiator/excel_component_test'),\n", - " App(filename='dsms-app-initiator/excel_nakajima_test/excel_nakajima_test.ipynb', basename='excel_nakajima_test.ipynb', folder='dsms-app-initiator/excel_nakajima_test'),\n", - " App(filename='dsms-app-initiator/excel_notch_test/excel_notch_tensile_test.ipynb', basename='excel_notch_tensile_test.ipynb', folder='dsms-app-initiator/excel_notch_test'),\n", - " App(filename='dsms-app-initiator/excel_shear_test/excel_shear_test.ipynb', basename='excel_shear_test.ipynb', folder='dsms-app-initiator/excel_shear_test'),\n", - " App(filename='dsms-app-initiator/excel_tensile_test/excel_tensile_test.ipynb', basename='excel_tensile_test.ipynb', folder='dsms-app-initiator/excel_tensile_test'),\n", - " App(filename='graph_viz/graph_viz.ipynb', basename='graph_viz.ipynb', folder='graph_viz'),\n", - " App(filename='tensile_test_bulk/csv_tensile_test_bulkupload.ipynb', basename='csv_tensile_test_bulkupload.ipynb', folder='tensile_test_bulk'),\n", - " App(filename='ternary-plot.argo.yaml', basename='ternary-plot.argo.yaml', folder=''),\n", - " App(filename='ternary_plot.ipynb', basename='ternary_plot.ipynb', folder='')]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" + "ename": "NameError", + "evalue": "name 'dsms' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[1], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mdsms\u001b[49m\u001b[38;5;241m.\u001b[39mapps_config\n", + "\u001b[1;31mNameError\u001b[0m: name 'dsms' is not defined" + ] } ], "source": [ - "dsms.apps" + "dsms.app_configs" ] }, { diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index 4c85492..e688119 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -21,10 +21,10 @@ { "executable": "testapp", "title": "data2rdf", - # "additional_properties": { - # "triggerUponUpload": True, - # "triggerUponUploadFileExtensions": [".xlsx"], - # }, + "additional_properties": { + "triggerUponUpload": True, + "triggerUponUploadFileExtensions": [".xlsx"], + }, } ], ) @@ -65,5 +65,5 @@ print(job.status) print("\n Current logs:") print(job.logs) - if job.status.phase == "Succeeded": + if job.status.phase != "Running": break From 4b8926194b9fb5179f95d10916ccf9a62d9528c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 9 Aug 2024 00:48:53 +0200 Subject: [PATCH 098/114] apply pre-commit hooks --- examples/create_and_run_app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index e688119..a862af3 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -21,10 +21,10 @@ { "executable": "testapp", "title": "data2rdf", - "additional_properties": { - "triggerUponUpload": True, - "triggerUponUploadFileExtensions": [".xlsx"], - }, + "additional_properties": { + "triggerUponUpload": True, + "triggerUponUploadFileExtensions": [".xlsx"], + }, } ], ) From e9d208bbdac0740402551d965f8eaa421a53f134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 9 Aug 2024 00:51:20 +0200 Subject: [PATCH 099/114] Bump version v1.4.0rc21 -> v1.4.0rc22 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 5a8af7e..67d42fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc21 +version = v1.4.0rc22 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From ce89a56a004473eb67142ac0de8727b8b0d3f4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 9 Aug 2024 17:04:38 +0200 Subject: [PATCH 100/114] add deletion of app configs, debug updating of app configs --- dsms/apps/config.py | 14 ++++++ dsms/apps/utils.py | 2 +- dsms/core/dsms.py | 19 ++++---- dsms/knowledge/utils.py | 21 ++++++--- examples/create_and_run_app.py | 82 +++++++++++++++++++++++++++++++--- examples/run_kitem_app.py | 27 ----------- 6 files changed, 118 insertions(+), 47 deletions(-) delete mode 100644 examples/run_kitem_app.py diff --git a/dsms/apps/config.py b/dsms/apps/config.py index 17b9ec5..31d8e3e 100644 --- a/dsms/apps/config.py +++ b/dsms/apps/config.py @@ -15,6 +15,7 @@ from dsms.apps.utils import ( # isort:skip _app_spec_exists, + _get_app_specification, ) @@ -142,6 +143,19 @@ def validate_specification(cls, self: "AppConfig") -> str: f"Invalid yaml specification path: `{error.args[0]}`" ) from error self.context.buffers.updated.update({self.name: self}) + elif isinstance(self.specification, dict) and self.in_backend: + spec = _get_app_specification(self.name) + if ( + not yaml.safe_load(spec) == self.specification + and self.name not in self.context.buffers.updated + ): + self.context.buffers.updated.update({self.name: self}) + elif ( + isinstance(self.specification, dict) + and not self.in_backend + and self.name not in self.context.buffers.updated + ): + self.context.buffers.updated.update({self.name: self}) return self @property diff --git a/dsms/apps/utils.py b/dsms/apps/utils.py index a5151fa..6c8e9fb 100644 --- a/dsms/apps/utils.py +++ b/dsms/apps/utils.py @@ -26,7 +26,7 @@ def _app_spec_exists(name: str) -> bool: def _get_app_specification(appname) -> str: response = _perform_request( - f"knowledge/api/apps/argo/spec/{appname}", + f"api/knowledge/apps/argo/spec/{appname}", "get", ) if not response.ok: diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index d71b003..0a91055 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -100,20 +100,23 @@ def __getitem__(self, key: str) -> "KItem": """Get KItem from remote DSMS instance.""" return _get_kitem(key) - def __delitem__(self, kitem) -> None: - """Stage an KItem for the deletion. + def __delitem__(self, obj) -> None: + """Stage an KItem, KType or AppConfig for the deletion. WARNING: Changes only will take place after executing the `commit`-method """ - from dsms.knowledge.kitem import ( # isort:skip - KItem, - ) + from dsms import KItem, AppConfig, KType # isort:skip - if not isinstance(kitem, KItem): + if isinstance(obj, KItem): + self.context.buffers.deleted.update({obj.id: obj}) + elif isinstance(obj, AppConfig): + self.context.buffers.deleted.update({obj.name: obj}) + elif isinstance(obj, KType): + raise NotImplementedError("Deletion of KTypes not available yet.") + else: raise TypeError( - f"Object must be of type {KItem}, not {type(kitem)}. " + f"Object must be of type {KItem}, {AppConfig} or {KType}, not {type(obj)}. " ) - kitem.context.buffers.deleted.update({kitem.id: kitem}) def commit(self) -> None: """Commit and empty the buffers of the KItems to the DSMS backend.""" diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 5769cfc..482e65f 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -372,6 +372,7 @@ def _upload_attachments(kitem: "KItem", attachment: "Attachment") -> None: f"""Invalid content type of attachment with name `{attachment.name}`: {type(attachment.content)}""" ) + file.name = attachment.name upload_file = {"dataFile": file} response = _perform_request( f"api/knowledge/attachments/{kitem.id}", @@ -610,9 +611,7 @@ def _commit_deleted( _delete_dataframe(obj.id) _delete_kitem(obj) elif isinstance(obj, AppConfig): - raise NotImplementedError( - "Deletion of AppConfigs not implemented yet." - ) + _delete_app_spec(obj.name) elif isinstance(obj, KType): raise NotImplementedError( "Deletion of KTypes not implemented yet." @@ -814,12 +813,24 @@ def _create_or_update_app_spec(app: "AppConfig", overwrite=False) -> None: """Create app specfication""" upload_file = {"def_file": io.StringIO(yaml.safe_dump(app.specification))} response = _perform_request( - f"/api/knowledge/apps/argo/{app.name}", + f"/api/knowledge/apps/argo/spec/{app.name}", "post", files=upload_file, params={"overwrite": overwrite}, ) if not response.ok: - message = f"Something went wrong uploading app with name `{app.name}`: {response.text}" + message = f"Something went wrong uploading app spec with name `{app.name}`: {response.text}" + raise RuntimeError(message) + return response.text + + +def _delete_app_spec(name: str) -> None: + """Delete app specfication""" + response = _perform_request( + f"/api/knowledge/apps/argo/spec/{name}", + "delete", + ) + if not response.ok: + message = f"Something went wrong deleting app spec with name `{name}`: {response.text}" raise RuntimeError(message) return response.text diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index a862af3..a81d20a 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -7,37 +7,103 @@ print("\nConnect to DSMS") dsms = DSMS(env="../.env") +configname = "testapp2" +kitem_name = "test item" + +data = """A,B,C +1.2,1.3,1.5 +1.7,1.8,1.9 +2.0,2.1,2.3 +2.5,2.6,2.8 +3.0,3.2,3.4 +3.6,3.7,3.9 +4.1,4.3,4.4 +4.7,4.8,5.0 +5.2,5.3,5.5 +5.8,6.0,6.1 +""" + +extension = ".csv" + +parameters = [ + {"name": "parser", "value": "csv"}, + {"name": "time_series_header_length", "value": 1}, + {"name": "metadata_length", "value": 0}, + {"name": "metadata_sep", "value": ","}, + {"name": "time_series_sep", "value": ","}, + { + "name": "mapping", + "value": """ + [ + { + "key": "A", + "iri": "https://w3id.org/steel/ProcessOntology/TestTime", + "unit": "s" + }, + { + "key": "B", + "iri": "https://w3id.org/steel/ProcessOntology/StandardForce", + "unit": "kN" + }, + { + "key": "C", + "iri": "https://w3id.org/steel/ProcessOntology/AbsoluteCrossheadTravel", + "unit": "mm" + } + ] + """, + }, +] + +specification = { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "Workflow", + "metadata": {"generateName": "data2rdf-"}, + "spec": { + "entrypoint": "execute_pipeline", + "workflowTemplateRef": {"name": "dsms-data2rdf"}, + "arguments": {"parameters": parameters}, + }, +} + print("\nCreate app specification") AppConfig( - name="testapp", - specification="../../dsms-app-initiator/excel_tensile_test.argo.yaml", + name=configname, + specification=specification, # this can also be a file path instead of a dict ) print("\nCreate kitem") item = KItem( - name="test123", + name=kitem_name, ktype_id=dsms.ktypes.Dataset, kitem_apps=[ { - "executable": "testapp", + "executable": configname, "title": "data2rdf", "additional_properties": { "triggerUponUpload": True, - "triggerUponUploadFileExtensions": [".xlsx"], + "triggerUponUploadFileExtensions": [extension], }, } ], + avatar={"include_qr": True}, ) + print("\nCommit KItem") dsms.commit() print("\nAdd attachment") -item.attachments = ["../../Desktop/AFZ1-Fz-S1D.xlsx"] +# here we are setting the content directly, +# but `item.attachments` can also be a list with a filepath +# e.g. item.attachments = ["path/to/my/file.csv"] +item.attachments = [{"name": "dummy_data.csv", "content": data}] +print(item) print("\nUpload attachment and trigger app") dsms.commit() + print(item) print("\nRun pipeline manually") @@ -67,3 +133,7 @@ print(job.logs) if job.status.phase != "Running": break + +print(item.url) + +print(item.dataframe.StandardForce.convert_to("N")) diff --git a/examples/run_kitem_app.py b/examples/run_kitem_app.py deleted file mode 100644 index 829a9ce..0000000 --- a/examples/run_kitem_app.py +++ /dev/null @@ -1,27 +0,0 @@ -import time - -from dsms import DSMS - -dsms = DSMS(env="../.env") - -id = "b24b3720-851a-445f-8028-77f526fc90b9" - -kitem = dsms[id] - -print("doing it synchronously") -job = kitem.kitem_apps.by_title["Fetch from CKAN"].run(transform=True) -print(job.artifacts) - - -print("doing it asychronously") -job = kitem.kitem_apps.by_title["Fetch from CKAN"].run( - transform=True, wait=False -) -while True: - time.sleep(1) - print("waiting...") - if job.status.phase == "Succeeded": - print("succeeded!") - break - print("task not ready yet...") -print(job.artifacts) From a7717a753df597c2eef06fbc2eec1a34f5efdc28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 12:22:00 +0200 Subject: [PATCH 101/114] introduce refresh option for kitem --- dsms/knowledge/kitem.py | 5 + dsms/knowledge/properties/apps.py | 2 + dsms/knowledge/utils.py | 21 ++-- examples/basic_usage.ipynb | 165 ++++++++++++++++-------------- examples/create_and_run_app.py | 21 +++- 5 files changed, 125 insertions(+), 89 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index f5c702d..c060dc6 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -57,6 +57,7 @@ _slugify, _inspect_dataframe, _make_annotation_schema, + _refresh_kitem, ) from dsms.knowledge.sparql_interface.utils import _get_subgraph # isort:skip @@ -625,6 +626,10 @@ def is_a(self, to_be_compared: KType) -> bool: == to_be_compared.value ) + def refresh(self) -> None: + """Refresh the KItem""" + _refresh_kitem(self) + def _get_ktype_as_str(self) -> str: if isinstance(self.ktype_id, str): ktype = self.ktype_id diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 324b254..6cd69f6 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -127,6 +127,8 @@ def run( f"Submission was not successful: {response.text}" ) submitted = response.json() + if wait: + self.kitem.refresh() return Job(name=submitted.get("name"), executable=self.executable) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 482e65f..a49326e 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -590,14 +590,7 @@ def _commit_updated_kitem(new_kitem: "KItem") -> None: logger.debug( "Fetching updated KItem from remote backend: %s", new_kitem.id ) - for key, value in _get_kitem(new_kitem.id, as_json=True).items(): - logger.debug( - "Set updated property `%s` for KItem with id `%s` after commiting: %s", - key, - new_kitem.id, - value, - ) - setattr(new_kitem, key, value) + new_kitem.refresh() def _commit_deleted( @@ -622,6 +615,18 @@ def _commit_deleted( ) +def _refresh_kitem(kitem: "KItem") -> None: + """Refresh the KItem""" + for key, value in _get_kitem(kitem.id, as_json=True).items(): + logger.debug( + "Set updated property `%s` for KItem with id `%s` after commiting: %s", + key, + kitem.id, + value, + ) + setattr(kitem, key, value) + + def _split_iri(iri: str) -> List[str]: if "#" in iri: namspace, name = iri.rsplit("#", 1) diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 52701f2..97d5c31 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -124,13 +124,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", + "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", "\n", "\tktype_id = KTypes.Dataset, \n", "\n", "\tin_backend = False, \n", "\n", - "\tslug = foo123-f0ff54d4, \n", + "\tslug = foo123-12a798aa, \n", "\n", "\tannotations = [], \n", "\n", @@ -198,7 +198,7 @@ { "data": { "text/plain": [ - "'https://bue.materials-data.space/knowledge/dataset/foo123-f0ff54d4'" + "'https://bue.materials-data.space/knowledge/dataset/foo123-12a798aa'" ] }, "execution_count": 5, @@ -230,13 +230,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", + "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-f0ff54d4, \n", + "\tslug = foo123-12a798aa, \n", "\n", "\tannotations = [], \n", "\n", @@ -256,9 +256,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-08-01 15:17:05.206635, \n", + "\tcreated_at = 2024-08-09 15:05:38.343459, \n", "\n", - "\tupdated_at = 2024-08-01 15:17:05.206635, \n", + "\tupdated_at = 2024-08-09 15:05:38.343459, \n", "\n", "\texternal_links = [], \n", "\n", @@ -352,13 +352,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", + "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-f0ff54d4, \n", + "\tslug = foo123-12a798aa, \n", "\n", "\tannotations = [\n", "\t\t{\n", @@ -399,9 +399,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-08-01 15:17:05.206635, \n", + "\tcreated_at = 2024-08-09 15:05:38.343459, \n", "\n", - "\tupdated_at = 2024-08-01 15:17:09.975357, \n", + "\tupdated_at = 2024-08-09 15:05:43.866336, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -617,13 +617,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = f0ff54d4-c2a9-42fc-b5e1-a43adcf5bae4, \n", + "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-f0ff54d4, \n", + "\tslug = foo123-12a798aa, \n", "\n", "\tannotations = [], \n", "\n", @@ -653,9 +653,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-08-01 15:17:05.206635, \n", + "\tcreated_at = 2024-08-09 15:05:38.343459, \n", "\n", - "\tupdated_at = 2024-08-01 15:17:09.975357, \n", + "\tupdated_at = 2024-08-09 15:05:43.866336, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -790,13 +790,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 056dc8e1-ab8d-47b9-a873-0ee603727ed2, \n", + " \tid = 85d47b16-0fff-40b7-9574-c05a79a0dc0e, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-056dc8e1, \n", + " \tslug = foo1-85d47b16, \n", " \n", " \tannotations = [], \n", " \n", @@ -804,9 +804,9 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", " \t\t\tname: foo 2\n", - " \t\t\tslug: foo2-de8f99cf\n", + " \t\t\tslug: foo2-21146fbf\n", " \t\t\tktype_id: organization\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", @@ -817,7 +817,7 @@ " \t\t\tdescription: None\n", " \t\t}]\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -828,8 +828,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-01T15:17:22.446849\n", - " \t\t\tupdated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\tcreated_at: 2024-08-09T15:06:00.156328\n", + " \t\t\tupdated_at: 2024-08-09T15:06:00.156328\n", " \t\t\n", " \t], \n", " \n", @@ -845,9 +845,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:21.860032, \n", + " \tcreated_at = 2024-08-09 15:05:59.532610, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:21.860032, \n", + " \tupdated_at = 2024-08-09 15:05:59.532610, \n", " \n", " \texternal_links = [], \n", " \n", @@ -893,13 +893,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 056dc8e1-ab8d-47b9-a873-0ee603727ed2, \n", + " \tid = 85d47b16-0fff-40b7-9574-c05a79a0dc0e, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-056dc8e1, \n", + " \tslug = foo1-85d47b16, \n", " \n", " \tannotations = [], \n", " \n", @@ -907,9 +907,9 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", " \t\t\tname: foo 2\n", - " \t\t\tslug: foo2-de8f99cf\n", + " \t\t\tslug: foo2-21146fbf\n", " \t\t\tktype_id: organization\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", @@ -920,7 +920,7 @@ " \t\t\tdescription: None\n", " \t\t}]\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -931,8 +931,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-01T15:17:22.446849\n", - " \t\t\tupdated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\tcreated_at: 2024-08-09T15:06:00.156328\n", + " \t\t\tupdated_at: 2024-08-09T15:06:00.156328\n", " \t\t\n", " \t], \n", " \n", @@ -948,9 +948,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:21.860032, \n", + " \tcreated_at = 2024-08-09 15:05:59.532610, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:21.860032, \n", + " \tupdated_at = 2024-08-09 15:05:59.532610, \n", " \n", " \texternal_links = [], \n", " \n", @@ -970,13 +970,13 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = de8f99cf-005e-4c5d-9710-c313ce12b55c, \n", + " \tid = 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo2-de8f99cf, \n", + " \tslug = foo2-21146fbf, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -991,15 +991,15 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", " \t\t\tname: foo 1\n", - " \t\t\tslug: foo1-056dc8e1\n", + " \t\t\tslug: foo1-85d47b16\n", " \t\t\tktype_id: dataset-catalog\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", " \t\t\tannotations: []\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -1010,8 +1010,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-01T15:17:21.860032\n", - " \t\t\tupdated_at: 2024-08-01T15:17:21.860032\n", + " \t\t\tcreated_at: 2024-08-09T15:05:59.532610\n", + " \t\t\tupdated_at: 2024-08-09T15:05:59.532610\n", " \t\t\n", " \t], \n", " \n", @@ -1027,9 +1027,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:22.446849, \n", + " \tcreated_at = 2024-08-09 15:06:00.156328, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:22.446849, \n", + " \tupdated_at = 2024-08-09 15:06:00.156328, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1049,13 +1049,13 @@ " \n", " \tname = foo 3, \n", " \n", - " \tid = e61ce089-a330-411b-bd9c-1e3c379d7f7a, \n", + " \tid = 67b425d5-edfa-4d55-8294-d060bfdba13a, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo3-e61ce089, \n", + " \tslug = foo3-67b425d5, \n", " \n", " \tannotations = [], \n", " \n", @@ -1075,9 +1075,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:23.003614, \n", + " \tcreated_at = 2024-08-09 15:06:00.875518, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:23.003614, \n", + " \tupdated_at = 2024-08-09 15:06:00.875518, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1097,13 +1097,13 @@ " \n", " \tname = foo 4, \n", " \n", - " \tid = f8fce715-2104-429f-8b2e-e2083c93b1ae, \n", + " \tid = 752ae260-d833-4b92-a99e-2b43c4ae3662, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo4-f8fce715, \n", + " \tslug = foo4-752ae260, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1130,9 +1130,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:23.571040, \n", + " \tcreated_at = 2024-08-09 15:06:01.576551, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:23.571040, \n", + " \tupdated_at = 2024-08-09 15:06:01.576551, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1178,13 +1178,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 056dc8e1-ab8d-47b9-a873-0ee603727ed2, \n", + " \tid = 85d47b16-0fff-40b7-9574-c05a79a0dc0e, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-056dc8e1, \n", + " \tslug = foo1-85d47b16, \n", " \n", " \tannotations = [], \n", " \n", @@ -1192,9 +1192,9 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", " \t\t\tname: foo 2\n", - " \t\t\tslug: foo2-de8f99cf\n", + " \t\t\tslug: foo2-21146fbf\n", " \t\t\tktype_id: organization\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", @@ -1205,7 +1205,7 @@ " \t\t\tdescription: None\n", " \t\t}]\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -1216,8 +1216,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-01T15:17:22.446849\n", - " \t\t\tupdated_at: 2024-08-01T15:17:22.446849\n", + " \t\t\tcreated_at: 2024-08-09T15:06:00.156328\n", + " \t\t\tupdated_at: 2024-08-09T15:06:00.156328\n", " \t\t\n", " \t], \n", " \n", @@ -1233,9 +1233,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:21.860032, \n", + " \tcreated_at = 2024-08-09 15:05:59.532610, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:21.860032, \n", + " \tupdated_at = 2024-08-09 15:05:59.532610, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1281,13 +1281,13 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = de8f99cf-005e-4c5d-9710-c313ce12b55c, \n", + " \tid = 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo2-de8f99cf, \n", + " \tslug = foo2-21146fbf, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1302,15 +1302,15 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 056dc8e1-ab8d-47b9-a873-0ee603727ed2\n", + " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", " \t\t\tname: foo 1\n", - " \t\t\tslug: foo1-056dc8e1\n", + " \t\t\tslug: foo1-85d47b16\n", " \t\t\tktype_id: dataset-catalog\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", " \t\t\tannotations: []\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: de8f99cf-005e-4c5d-9710-c313ce12b55c\n", + " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -1321,8 +1321,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-01T15:17:21.860032\n", - " \t\t\tupdated_at: 2024-08-01T15:17:21.860032\n", + " \t\t\tcreated_at: 2024-08-09T15:05:59.532610\n", + " \t\t\tupdated_at: 2024-08-09T15:05:59.532610\n", " \t\t\n", " \t], \n", " \n", @@ -1338,9 +1338,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-01 15:17:22.446849, \n", + " \tcreated_at = 2024-08-09 15:06:00.156328, \n", " \n", - " \tupdated_at = 2024-08-01 15:17:22.446849, \n", + " \tupdated_at = 2024-08-09 15:06:00.156328, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1406,19 +1406,26 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 23, "metadata": {}, "outputs": [ { - "ename": "NameError", - "evalue": "name 'dsms' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[1], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mdsms\u001b[49m\u001b[38;5;241m.\u001b[39mapps_config\n", - "\u001b[1;31mNameError\u001b[0m: name 'dsms' is not defined" - ] + "data": { + "text/plain": [ + "[AppConfig(name=ckan-fetch, specification={'metadata': {'generateName': 'ckan-resource-request-'}}),\n", + " AppConfig(name=csv_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=csv_tensile_test_f2, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_notched_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_shear_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=ternary-plot, specification={'metadata': {'generateName': 'ckan-tenary-app-'}}),\n", + " AppConfig(name=testapp, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=testapp2, specification={'metadata': {'generateName': 'data2rdf-'}})]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -1441,7 +1448,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 24, "metadata": {}, "outputs": [ { diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index a81d20a..7e79fb8 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -67,7 +67,7 @@ } print("\nCreate app specification") -AppConfig( +appspec = AppConfig( name=configname, specification=specification, # this can also be a file path instead of a dict ) @@ -78,7 +78,7 @@ ktype_id=dsms.ktypes.Dataset, kitem_apps=[ { - "executable": configname, + "executable": appspec.name, "title": "data2rdf", "additional_properties": { "triggerUponUpload": True, @@ -103,6 +103,12 @@ print("\nUpload attachment and trigger app") dsms.commit() +print("\nGet dataframe") +if not item.dataframe: + time.sleep(5) + item.refresh() +print(item.dataframe.StandardForce.convert_to("N")) + print(item) @@ -111,6 +117,7 @@ attachment_name=item.attachments[0].name, set_token=True, set_host_url=True ) + print("\nSee job status") print(job.status) print("\n See job logs:") @@ -124,6 +131,9 @@ wait=False, ) +print("\nGet dataframe") +print(item.dataframe.StandardForce.convert_to("N")) + print("\nMonitor job status") while True: time.sleep(1) @@ -134,6 +144,13 @@ if job.status.phase != "Running": break +item.refresh() + print(item.url) +print("\nGet dataframe") print(item.dataframe.StandardForce.convert_to("N")) + +print("\nCleanup") +del dsms[item] +del dsms[appspec] From 40476ee7d6a257c405a83a8b50117ac323ca4c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 12:25:35 +0200 Subject: [PATCH 102/114] Bump version v1.4.0rc22 -> v1.4.0rc23 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 67d42fc..a0ac790 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc22 +version = v1.4.0rc23 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 29581fdbf0322520c632e12d3076ee0f9bfc98d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 13:01:58 +0200 Subject: [PATCH 103/114] add pyyaml to requirements --- examples/create_and_run_app.py | 3 --- setup.cfg | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index 7e79fb8..4ab32a4 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -104,9 +104,6 @@ dsms.commit() print("\nGet dataframe") -if not item.dataframe: - time.sleep(5) - item.refresh() print(item.dataframe.StandardForce.convert_to("N")) diff --git a/setup.cfg b/setup.cfg index a0ac790..f5fac6f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ classifiers = [options] packages = find: install_requires = + PyYAML>=6,<7 click>=8,<9 html5lib>=1,<2 lru-cache<1 From de9373c69143fac3ace94bdb6d6cf768f329b979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 13:02:27 +0200 Subject: [PATCH 104/114] Bump version v1.4.0rc23 -> v1.4.0rc24 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f5fac6f..e474227 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc23 +version = v1.4.0rc24 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 88c9de9a1a63b44fe5effe444fcd9be15ba2e2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 15:34:43 +0200 Subject: [PATCH 105/114] update kitem refreshing also for dataframes --- dsms/knowledge/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index a49326e..b4fc4b0 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -625,6 +625,7 @@ def _refresh_kitem(kitem: "KItem") -> None: value, ) setattr(kitem, key, value) + kitem.dataframe = _inspect_dataframe(kitem.id) def _split_iri(iri: str) -> List[str]: From c409437dc6cad3591a251ebd3947e9f76fdfdba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 15:38:40 +0200 Subject: [PATCH 106/114] Bump version v1.4.0rc24 -> v1.4.0rc25 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e474227..ba860c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v1.4.0rc24 +version = v1.4.0rc25 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown From 39137de328e432ed9106027b384cd7f9c1b2729d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 16:57:40 +0200 Subject: [PATCH 107/114] debug deletion of dataframes --- dsms/knowledge/kitem.py | 10 +++------- dsms/knowledge/utils.py | 21 ++++++++++++--------- examples/create_and_run_app.py | 23 ++++++++++++++++++----- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index c060dc6..5565bd8 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -509,8 +509,8 @@ def validate_avatar(cls, value: "Union[Dict, Avatar]") -> Avatar: @classmethod def validate_dataframe( cls, - value: Union[ - List[Column], pd.DataFrame, Dict[str, Dict[Any, Any]] + value: Optional[ + Union[List[Column], pd.DataFrame, Dict[str, Dict[Any, Any]]] ], # pylint: disable=unused-argument info: ValidationInfo, ) -> DataFrameContainer: @@ -519,12 +519,8 @@ def validate_dataframe( if isinstance(value, (pd.DataFrame, dict)): if isinstance(value, pd.DataFrame): dataframe = value.copy(deep=True) - elif isinstance(value, dict): - dataframe = pd.DataFrame.from_dict(value) else: - raise TypeError( - f"Data must be of type {dict} or {pd.DataFrame}, not {type(value)}" - ) + dataframe = pd.DataFrame.from_dict(value) else: columns = _inspect_dataframe(kitem_id) if columns: diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index b4fc4b0..277528e 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -727,16 +727,19 @@ def _inspect_dataframe(kitem_id: str) -> Optional[List[Dict[str, Any]]]: def _update_dataframe(kitem_id: str, data: pd.DataFrame): - buffer = io.BytesIO() - data.to_json(buffer, indent=2) - buffer.seek(0) - response = _perform_request( - f"api/knowledge/data_api/{kitem_id}", "put", files={"data": buffer} - ) - if not response.ok: - raise RuntimeError( - f"Could not put dataframe into kitem with id `{kitem_id}`: {response.text}" + if data.empty: + _delete_dataframe(kitem_id) + else: + buffer = io.BytesIO() + data.to_json(buffer, indent=2) + buffer.seek(0) + response = _perform_request( + f"api/knowledge/data_api/{kitem_id}", "put", files={"data": buffer} ) + if not response.ok: + raise RuntimeError( + f"Could not put dataframe into kitem with id `{kitem_id}`: {response.text}" + ) def _delete_dataframe(kitem_id: str) -> Response: diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index 4ab32a4..5aac985 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -31,6 +31,7 @@ {"name": "metadata_length", "value": 0}, {"name": "metadata_sep", "value": ","}, {"name": "time_series_sep", "value": ","}, + {"name": "log_level", "value": "DEBUG"}, { "name": "mapping", "value": """ @@ -98,6 +99,7 @@ # e.g. item.attachments = ["path/to/my/file.csv"] item.attachments = [{"name": "dummy_data.csv", "content": data}] +print("\nVerify that the attachment was added") print(item) print("\nUpload attachment and trigger app") @@ -107,6 +109,9 @@ print(item.dataframe.StandardForce.convert_to("N")) +print("\nVerify that the dataframe was deleted") +item.dataframe = {} +dsms.commit() print(item) print("\nRun pipeline manually") @@ -114,12 +119,21 @@ attachment_name=item.attachments[0].name, set_token=True, set_host_url=True ) - print("\nSee job status") print(job.status) print("\n See job logs:") print(job.logs) +print("\nGet dataframe") +print(item.dataframe.StandardForce.convert_to("N")) + +print("\nDelete dataframe") +item.dataframe = {} +dsms.commit() + +print("\nVerify that the dataframe was deleted") +print(item) + print("\nRun pipeline manually in the background") job = item.kitem_apps.by_title["data2rdf"].run( attachment_name=item.attachments[0].name, @@ -128,8 +142,6 @@ wait=False, ) -print("\nGet dataframe") -print(item.dataframe.StandardForce.convert_to("N")) print("\nMonitor job status") while True: @@ -141,13 +153,14 @@ if job.status.phase != "Running": break +print("\nReload item") item.refresh() -print(item.url) - print("\nGet dataframe") print(item.dataframe.StandardForce.convert_to("N")) print("\nCleanup") del dsms[item] del dsms[appspec] + +dsms.commit() From a3ad95592f65a8b1202677df5b02d3840d387258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Fri, 16 Aug 2024 20:04:01 +0200 Subject: [PATCH 108/114] refactor docs --- .gitattributes | 4 - Dockerfile.docs | 42 +-- README.md | 1 - docs/Makefile | 161 ++++++++++- docs/README.md | 4 +- docs/{source => }/_static/custom.css | 0 docs/{source => }/assets/images/DSMS.jpg | Bin docs/{source => }/assets/images/DSMS_Mod.jpg | Bin docs/{source => }/assets/images/DSMS_SDK.jpg | Bin docs/{source => }/assets/images/DSMS_logo.png | Bin .../assets/images/UML_KItem_schema.jpg | Bin .../assets/images/copy_token_1.jpg | Bin .../assets/images/copy_token_2.jpg | Bin .../assets/images/copy_token_3.jpg | Bin .../assets/images/copy_token_4.jpg | Bin .../assets/images/copy_token_5.jpg | Bin docs/{source => }/conf.py | 18 ++ docs/describe.md | 27 -- docs/{source => }/dsms.md | 0 docs/{source => }/dsms_config_schema.md | 0 docs/{source => }/dsms_kitem_schema.md | 0 docs/{source => }/dsms_sdk.md | 0 docs/{source => }/index.md | 0 docs/requirements.txt | 13 - .../tutorials/1_introduction.ipynb | 0 docs/{source => }/tutorials/2_creation.ipynb | 0 docs/{source => }/tutorials/3_updation.ipynb | 0 docs/{source => }/tutorials/4_deletion.ipynb | 0 docs/{source => }/tutorials/5_search.ipynb | 0 docs/{source => }/tutorials/6_app_HDF5.ipynb | 0 requirements_PCL590_thesis.txt | 265 ------------------ setup.cfg | 14 + 32 files changed, 195 insertions(+), 354 deletions(-) delete mode 100644 .gitattributes rename docs/{source => }/_static/custom.css (100%) rename docs/{source => }/assets/images/DSMS.jpg (100%) rename docs/{source => }/assets/images/DSMS_Mod.jpg (100%) rename docs/{source => }/assets/images/DSMS_SDK.jpg (100%) rename docs/{source => }/assets/images/DSMS_logo.png (100%) rename docs/{source => }/assets/images/UML_KItem_schema.jpg (100%) rename docs/{source => }/assets/images/copy_token_1.jpg (100%) rename docs/{source => }/assets/images/copy_token_2.jpg (100%) rename docs/{source => }/assets/images/copy_token_3.jpg (100%) rename docs/{source => }/assets/images/copy_token_4.jpg (100%) rename docs/{source => }/assets/images/copy_token_5.jpg (100%) rename docs/{source => }/conf.py (88%) delete mode 100644 docs/describe.md rename docs/{source => }/dsms.md (100%) rename docs/{source => }/dsms_config_schema.md (100%) rename docs/{source => }/dsms_kitem_schema.md (100%) rename docs/{source => }/dsms_sdk.md (100%) rename docs/{source => }/index.md (100%) delete mode 100644 docs/requirements.txt rename docs/{source => }/tutorials/1_introduction.ipynb (100%) rename docs/{source => }/tutorials/2_creation.ipynb (100%) rename docs/{source => }/tutorials/3_updation.ipynb (100%) rename docs/{source => }/tutorials/4_deletion.ipynb (100%) rename docs/{source => }/tutorials/5_search.ipynb (100%) rename docs/{source => }/tutorials/6_app_HDF5.ipynb (100%) delete mode 100644 requirements_PCL590_thesis.txt diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index cbc003c..0000000 --- a/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ - -*.ipynb diff=jupyternotebook - -*.ipynb merge=jupyternotebook diff --git a/Dockerfile.docs b/Dockerfile.docs index cfb8d91..9642675 100644 --- a/Dockerfile.docs +++ b/Dockerfile.docs @@ -1,34 +1,22 @@ -# Use a base image with the latest available Python version (adjust as necessary) -FROM python:3.9-buster +FROM python:3.10 -# Ensure the package list is up-to-date and upgrade existing packages -RUN apt-get update && apt-get upgrade -y +RUN apt-get update && apt-get install -y \ + pandoc default-jre graphviz \ + texlive-latex-recommended \ + texlive-latex-extra \ + texlive-fonts-recommended \ + latexmk -# Install required packages, ensuring the latest versions are installed -RUN apt-get install -y --no-install-recommends pandoc default-jre graphviz -RUN apt-get install -y --no-install-recommends texlive-latex-recommended \ - texlive-latex-extra \ - texlive-fonts-recommended \ - latexmk - -# Clean up the apt cache to reduce image size -RUN apt-get clean && rm -rf /var/lib/apt/lists/* - -# Set the working directory WORKDIR /app +ADD . . -# Copy the requirements file for documentation -COPY docs/requirements.txt /app/docs/requirements.txt - -# Install Python packages for documentation from requirements.txt, ensuring the latest versions are installed -RUN pip install --upgrade pip -RUN pip install --upgrade -r /app/docs/requirements.txt +RUN python -m pip install --upgrade pip +RUN python -m pip install -e .[docs] -# Copy the entire repository to the container -COPY . /app +CMD sphinx-autobuild --host 0.0.0.0 docs/ docs/_build/html -# Install the SDK in editable mode -RUN pip install -e /app +# Build: +# $ docker build -f Dockerfile.docs -t dsms-sdk-docs . -# Define the command to run the application -CMD ["sphinx-autobuild", "--host", "0.0.0.0", "docs/source", "docs/build/html"] +# Run: +# $ docker run -it --rm -v $PWD:/app -p 8000:8000 dsms-sdk-docs diff --git a/README.md b/README.md index 7703aba..d81c8da 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ The SDK provides a general Python interface to a remote DSMS deployment, allowin For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items. - ## Disclaimer Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM. diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf..caa42d9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,20 +1,153 @@ -# Minimal makefile for Sphinx documentation +# Makefile for Sphinx documentation # -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext -# Put it first so that "make" without argument is like "make help". help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/AWSSDKforPHP.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/AWSSDKforPHP.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/AWSSDKforPHP" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/AWSSDKforPHP" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." -.PHONY: help Makefile +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/README.md b/docs/README.md index b426787..3766da6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,4 @@ # DSMS-SDK Docs -To access the documentation, please visit: - If you find any error or problem with the documentation, please create an issue [in this repository](https://github.com/MI-FraunhoferIWM/dsms-python-sdk/issues). @@ -33,7 +31,7 @@ $ sudo apt-get install texlive-latex-recommended \ ``` The python dependencies: ```shell -$ pip install -r requirements.txt +$ pip install -e .[docs] ``` Now you can start the server and render the docs: diff --git a/docs/source/_static/custom.css b/docs/_static/custom.css similarity index 100% rename from docs/source/_static/custom.css rename to docs/_static/custom.css diff --git a/docs/source/assets/images/DSMS.jpg b/docs/assets/images/DSMS.jpg similarity index 100% rename from docs/source/assets/images/DSMS.jpg rename to docs/assets/images/DSMS.jpg diff --git a/docs/source/assets/images/DSMS_Mod.jpg b/docs/assets/images/DSMS_Mod.jpg similarity index 100% rename from docs/source/assets/images/DSMS_Mod.jpg rename to docs/assets/images/DSMS_Mod.jpg diff --git a/docs/source/assets/images/DSMS_SDK.jpg b/docs/assets/images/DSMS_SDK.jpg similarity index 100% rename from docs/source/assets/images/DSMS_SDK.jpg rename to docs/assets/images/DSMS_SDK.jpg diff --git a/docs/source/assets/images/DSMS_logo.png b/docs/assets/images/DSMS_logo.png similarity index 100% rename from docs/source/assets/images/DSMS_logo.png rename to docs/assets/images/DSMS_logo.png diff --git a/docs/source/assets/images/UML_KItem_schema.jpg b/docs/assets/images/UML_KItem_schema.jpg similarity index 100% rename from docs/source/assets/images/UML_KItem_schema.jpg rename to docs/assets/images/UML_KItem_schema.jpg diff --git a/docs/source/assets/images/copy_token_1.jpg b/docs/assets/images/copy_token_1.jpg similarity index 100% rename from docs/source/assets/images/copy_token_1.jpg rename to docs/assets/images/copy_token_1.jpg diff --git a/docs/source/assets/images/copy_token_2.jpg b/docs/assets/images/copy_token_2.jpg similarity index 100% rename from docs/source/assets/images/copy_token_2.jpg rename to docs/assets/images/copy_token_2.jpg diff --git a/docs/source/assets/images/copy_token_3.jpg b/docs/assets/images/copy_token_3.jpg similarity index 100% rename from docs/source/assets/images/copy_token_3.jpg rename to docs/assets/images/copy_token_3.jpg diff --git a/docs/source/assets/images/copy_token_4.jpg b/docs/assets/images/copy_token_4.jpg similarity index 100% rename from docs/source/assets/images/copy_token_4.jpg rename to docs/assets/images/copy_token_4.jpg diff --git a/docs/source/assets/images/copy_token_5.jpg b/docs/assets/images/copy_token_5.jpg similarity index 100% rename from docs/source/assets/images/copy_token_5.jpg rename to docs/assets/images/copy_token_5.jpg diff --git a/docs/source/conf.py b/docs/conf.py similarity index 88% rename from docs/source/conf.py rename to docs/conf.py index 714319f..538bd2a 100644 --- a/docs/source/conf.py +++ b/docs/conf.py @@ -80,3 +80,21 @@ def setup(app): } nbsphinx_allow_errors = True + +# -- Options for LaTeX output ------------------------------------------------- +latex_documents = [ + ( + "index", + "dsms_docs.tex", + "DSMS docs", + ("Materials Informatics team at Fraunhofer IWM"), + "manual", + "false", + ) +] +latex_logo = "assets/images/DSMS_logo.png" +latex_elements = {"figure_align": "H"} + +nbsphinx_allow_errors = True + +suppress_warnings = ["myst.mathjax"] diff --git a/docs/describe.md b/docs/describe.md deleted file mode 100644 index ef262b0..0000000 --- a/docs/describe.md +++ /dev/null @@ -1,27 +0,0 @@ -This code is part of the dsms-python-sdk package located at D:\HiWi\dsms-python-sdk\dsms. - -The dsms-python-sdk package provides a set of tools and utilities for interacting with the DSMS (Data Science Management System) platform. It offers functionality for data ingestion, data processing, model training, and model deployment. - -The package is organized into the following packages: - -- `dsms.core`: Contains the core functionality of the DSMS SDK, including authentication, API client, and utility functions. -- `dsms.data`: Provides classes and functions for data ingestion and preprocessing, such as data loaders, transformers, and validators. -- `dsms.models`: Includes classes and functions for model training and evaluation, including model builders, trainers, and evaluators. -- `dsms.deploy`: Offers functionality for deploying trained models to the DSMS platform, including model packaging, versioning, and deployment management. - -Please refer to the individual package documentation for more details on their usage and available functionalities. - -This module contains the implementation of the dsms-python-sdk package. - -The dsms-python-sdk package provides a set of tools and utilities for interacting with the DSMS (Data Science Management System) platform. - -Usage: - import dsms - - # Example usage - client = dsms.Client() - client.login(username='user', password='pass') - project = client.get_project(project_id='12345') - ... - -For more information, please refer to the official documentation at https://github.com/username/dsms-python-sdk. diff --git a/docs/source/dsms.md b/docs/dsms.md similarity index 100% rename from docs/source/dsms.md rename to docs/dsms.md diff --git a/docs/source/dsms_config_schema.md b/docs/dsms_config_schema.md similarity index 100% rename from docs/source/dsms_config_schema.md rename to docs/dsms_config_schema.md diff --git a/docs/source/dsms_kitem_schema.md b/docs/dsms_kitem_schema.md similarity index 100% rename from docs/source/dsms_kitem_schema.md rename to docs/dsms_kitem_schema.md diff --git a/docs/source/dsms_sdk.md b/docs/dsms_sdk.md similarity index 100% rename from docs/source/dsms_sdk.md rename to docs/dsms_sdk.md diff --git a/docs/source/index.md b/docs/index.md similarity index 100% rename from docs/source/index.md rename to docs/index.md diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index d9fd575..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -sphinx==3.5.3 -myst-parser==0.15.2 -sphinx_rtd_theme==0.5.2 -sphinxcontrib-plantuml==0.20.1 -nbsphinx==0.8.2 -sphinx-copybutton==0.3.1 -ipython==7.22.0 -jupyter==1.0.0 -sphinx-autobuild==2021.3.14 -sphinx-panels==0.5.2 -jinja2==3.0.3 -sphinx-markdown-tables -sphinxcontrib-redoc==1.6.0 diff --git a/docs/source/tutorials/1_introduction.ipynb b/docs/tutorials/1_introduction.ipynb similarity index 100% rename from docs/source/tutorials/1_introduction.ipynb rename to docs/tutorials/1_introduction.ipynb diff --git a/docs/source/tutorials/2_creation.ipynb b/docs/tutorials/2_creation.ipynb similarity index 100% rename from docs/source/tutorials/2_creation.ipynb rename to docs/tutorials/2_creation.ipynb diff --git a/docs/source/tutorials/3_updation.ipynb b/docs/tutorials/3_updation.ipynb similarity index 100% rename from docs/source/tutorials/3_updation.ipynb rename to docs/tutorials/3_updation.ipynb diff --git a/docs/source/tutorials/4_deletion.ipynb b/docs/tutorials/4_deletion.ipynb similarity index 100% rename from docs/source/tutorials/4_deletion.ipynb rename to docs/tutorials/4_deletion.ipynb diff --git a/docs/source/tutorials/5_search.ipynb b/docs/tutorials/5_search.ipynb similarity index 100% rename from docs/source/tutorials/5_search.ipynb rename to docs/tutorials/5_search.ipynb diff --git a/docs/source/tutorials/6_app_HDF5.ipynb b/docs/tutorials/6_app_HDF5.ipynb similarity index 100% rename from docs/source/tutorials/6_app_HDF5.ipynb rename to docs/tutorials/6_app_HDF5.ipynb diff --git a/requirements_PCL590_thesis.txt b/requirements_PCL590_thesis.txt deleted file mode 100644 index d1a8f01..0000000 --- a/requirements_PCL590_thesis.txt +++ /dev/null @@ -1,265 +0,0 @@ -# This file may be used to create an environment using: -# $ conda create --name --file -# platform: win-64 -@EXPLICIT -https://repo.anaconda.com/pkgs/main/win-64/blas-1.0-mkl.conda -https://repo.anaconda.com/pkgs/main/win-64/ca-certificates-2024.3.11-haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_1.conda -https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.1.0-h57928b3_964.conda -https://conda.anaconda.org/conda-forge/win-64/libasprintf-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/win-64/libexpat-2.6.2-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/msys2-conda-epoch-20160418-1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/pandoc-3.1.13-h57928b3_0.conda -https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 -https://repo.anaconda.com/pkgs/main/noarch/tzdata-2024a-h04d1e81_0.conda -https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 -https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/expat-2.6.2-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libasprintf-devel-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/win-64/m2w64-gmp-6.1.0-2.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/m2w64-libwinpthread-git-5.0.0.4634.697f757-2.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.38.33130-h82b7239_18.conda -https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.38.33130-hcb4865c_18.conda -https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/vc-14.2-h21ff451_1.conda -https://conda.anaconda.org/conda-forge/win-64/aom-3.8.2-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda -https://conda.anaconda.org/conda-forge/win-64/dav1d-1.2.1-hcfcfb64_0.conda -https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.3.0-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/freeglut-3.2.2-h63175ca_2.conda -https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.13-h63175ca_1003.conda -https://conda.anaconda.org/conda-forge/win-64/icu-73.2-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/khronos-opencl-icd-loader-2023.04.17-h64bf75a_0.conda -https://conda.anaconda.org/conda-forge/win-64/lerc-4.0.0-h63175ca_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libabseil-20240116.1-cxx17_h63175ca_2.conda -https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.3-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.20-hcfcfb64_0.conda -https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libiconv-1.17-hcfcfb64_2.conda -https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.0.0-hcfcfb64_1.conda -https://conda.anaconda.org/conda-forge/win-64/libopus-1.3.1-h8ffe710_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.18-h8d14728_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.2-hcfcfb64_0.conda -https://repo.anaconda.com/pkgs/main/win-64/libuv-1.44.2-h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.3.2-hcfcfb64_0.conda -https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda -https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/mpfr-4.0.2-h62dcd97_1.conda -https://repo.anaconda.com/pkgs/main/win-64/mpir-3.0.0-hec2e145_1.conda -https://conda.anaconda.org/conda-forge/win-64/openh264-2.4.1-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.1-hcfcfb64_1.conda -https://conda.anaconda.org/conda-forge/win-64/pixman-0.43.4-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/pthreads-win32-2.9.1-hfa6e2cd_3.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/pugixml-1.14-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/snappy-1.1.10-hfb803bf_0.conda -https://repo.anaconda.com/pkgs/main/win-64/sqlite-3.41.2-h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/win-64/svt-av1-2.0.0-h63175ca_0.conda -https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda -https://conda.anaconda.org/conda-forge/win-64/x264-1!164.3095-h8ffe710_2.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/x265-3.5-h2d74725_3.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/yaml-0.2.5-he774522_0.conda -https://conda.anaconda.org/conda-forge/win-64/imath-3.1.11-h12be248_0.conda -https://conda.anaconda.org/conda-forge/win-64/jasper-4.2.3-h28f2b1a_0.conda -https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.2-heb0366b_0.conda -https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.43-h19919ed_0.conda -https://conda.anaconda.org/conda-forge/win-64/libprotobuf-4.25.3-h503648d_0.conda -https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.0-h7dfc565_0.conda -https://conda.anaconda.org/conda-forge/win-64/libxml2-2.12.6-hc3477c8_1.conda -https://repo.anaconda.com/pkgs/main/win-64/mpc-1.1.0-h7edee0f_1.conda -https://conda.anaconda.org/conda-forge/win-64/pcre2-10.43-h17e33f8_0.conda -https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hcd874cb_1001.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/python-3.9.19-h1aa4202_0.conda -https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.11-hcd874cb_0.conda -https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.3-hcd874cb_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h63175ca_1.conda -https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.13-hcfcfb64_5.conda -https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.5-h12be248_0.conda -https://conda.anaconda.org/conda-forge/noarch/attrs-23.2.0-pyh71513ae_0.conda -https://repo.anaconda.com/pkgs/main/win-64/brotli-python-1.0.9-py39hd77b12b_7.conda -https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/certifi-2024.2.2-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda -https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_2.conda -https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/filelock-3.13.1-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda -https://conda.anaconda.org/conda-forge/win-64/gettext-tools-0.22.5-h7d00a51_2.conda -https://repo.anaconda.com/pkgs/main/win-64/gmpy2-2.1.2-py39h7f96b67_0.conda -https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/idna-3.4-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/json5-0.9.24-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.10-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/libclang13-18.1.2-default_hf64faad_1.conda -https://conda.anaconda.org/conda-forge/win-64/libcurl-8.7.1-hd5e4a3a_0.conda -https://conda.anaconda.org/conda-forge/win-64/libgettextpo-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/win-64/libglib-2.80.0-h39d0aa6_3.conda -https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.9.3-default_haede6df_1009.conda -https://conda.anaconda.org/conda-forge/win-64/libintl-devel-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/win-64/libtiff-4.6.0-hddb2be6_3.conda -https://conda.anaconda.org/conda-forge/win-64/libxcb-1.15-hcd874cb_0.conda -https://repo.anaconda.com/pkgs/main/win-64/markupsafe-2.1.3-py39h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/noarch/mistune-3.0.2-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/mpmath-1.3.0-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/networkx-3.1-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/win-64/openexr-3.2.2-h72640d8_1.conda -https://conda.anaconda.org/conda-forge/noarch/packaging-24.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-py_1003.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_1.conda -https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.2.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.20.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.2-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/pygments-2.17.2-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.19.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2024.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/python_abi-3.9-2_cp39.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pyyaml-6.0.1-py39h2bbff1b_0.conda -https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/setuptools-68.2.2-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.5-pyhd8ed1ab_1.conda -https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.2-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20240316-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/typing_extensions-4.9.0-py39haa95532_1.conda -https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/webcolors-1.13-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_2.conda -https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.7.0-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/wheel-0.41.2-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.10-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/win_inet_pton-1.1.0-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/zipp-3.17.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/anyio-4.3.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/asttokens-2.4.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.4-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/babel-2.14.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda -https://conda.anaconda.org/conda-forge/noarch/bleach-6.1.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/cffi-1.16.0-py39ha55989b_0.conda -https://conda.anaconda.org/conda-forge/noarch/comm-0.2.2-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.1-py39h99910a6_0.conda -https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.14.2-hbde0cde_0.conda -https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.3-nompi_h73e8ff5_100.conda -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-7.1.0-pyha770c72_0.conda -https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.1-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/jinja2-3.1.3-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/win-64/jsonpointer-2.4-py39hcbf5309_3.conda -https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_1.conda -https://conda.anaconda.org/conda-forge/win-64/lcms2-2.16-h67d730c_0.conda -https://conda.anaconda.org/conda-forge/win-64/libgettextpo-devel-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.6-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.2-h3d672ee_0.conda -https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pip-23.3.1-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.42-pyha770c72_0.conda -https://conda.anaconda.org/conda-forge/win-64/psutil-5.9.8-py39ha55989b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/pysocks-1.7.1-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/pywin32-306-py39h99910a6_2.conda -https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.13-py39h99910a6_0.conda -https://conda.anaconda.org/conda-forge/win-64/pyzmq-25.1.2-py39h7eaf5a6_0.conda -https://conda.anaconda.org/conda-forge/noarch/qtpy-2.4.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.18.0-py39hf21820d_0.conda -https://repo.anaconda.com/pkgs/main/win-64/sympy-1.12-py39haa95532_0.conda -https://conda.anaconda.org/conda-forge/win-64/tbb-2021.11.0-h91493d7_1.conda -https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.2.1-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/tornado-6.4-py39ha55989b_0.conda -https://repo.anaconda.com/pkgs/main/win-64/typing-extensions-4.9.0-py39haa95532_1.conda -https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-21.2.0-py39ha55989b_4.conda -https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.0-h1fef639_0.conda -https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/win-64/gettext-0.22.5-h5728263_2.conda -https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.5-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-7.1.0-hd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/jupyter_core-5.7.2-py39hcbf5309_0.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-2024.0.0-hc2557fa_4.conda -https://conda.anaconda.org/conda-forge/win-64/mkl-2024.1.0-h66d3029_692.conda -https://conda.anaconda.org/conda-forge/win-64/pillow-10.3.0-py39h9ee4981_0.conda -https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.42-hd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/referencing-0.34.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.2-pyh08f2357_0.conda -https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.2-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda -https://repo.anaconda.com/pkgs/main/win-64/urllib3-2.1.0-py39haa95532_1.conda -https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-23.1.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/harfbuzz-8.3.0-h7ab893a_0.conda -https://conda.anaconda.org/conda-forge/noarch/httpx-0.27.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/ipython-8.18.1-pyh7428d3b_3.conda -https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_0.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2023.12.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-22_win64_mkl.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-auto-batch-plugin-2024.0.0-h002f227_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-auto-plugin-2024.0.0-h002f227_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-hetero-plugin-2024.0.0-h7e3b17c_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-intel-cpu-plugin-2024.0.0-hc2557fa_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-intel-gpu-plugin-2024.0.0-hc2557fa_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-ir-frontend-2024.0.0-h7e3b17c_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-onnx-frontend-2024.0.0-h55b4db4_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-paddle-frontend-2024.0.0-h55b4db4_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-pytorch-frontend-2024.0.0-h63175ca_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-tensorflow-frontend-2024.0.0-ha362bc9_4.conda -https://conda.anaconda.org/conda-forge/win-64/libopenvino-tensorflow-lite-frontend-2024.0.0-h63175ca_4.conda -https://conda.anaconda.org/pytorch/win-64/pytorch-2.2.2-py3.9_cpu_0.tar.bz2 -https://repo.anaconda.com/pkgs/main/win-64/requests-2.31.0-py39haa95532_1.conda -https://conda.anaconda.org/conda-forge/win-64/ffmpeg-6.1.1-gpl_h66c0b5b_107.conda -https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.29.3-pyha63f2e9_0.conda -https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.2-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.21.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-22_win64_mkl.conda -https://conda.anaconda.org/conda-forge/win-64/liblapack-3.9.0-22_win64_mkl.conda -https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.6.3-h5dee92f_0.conda -https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.21.1-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter_console-6.6.3-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.9.0-22_win64_mkl.conda -https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/numpy-1.26.4-py39hddb5d58_0.conda -https://conda.anaconda.org/conda-forge/noarch/qtconsole-base-5.5.1-pyha770c72_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.10.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/libopencv-4.9.0-qt6_py39h45fa9c4_612.conda -https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/pandas-2.2.1-py39h32e6231_0.conda -https://conda.anaconda.org/pytorch/win-64/torchaudio-2.2.2-py39_cpu.tar.bz2 -https://conda.anaconda.org/pytorch/win-64/torchvision-0.17.2-py39_cpu.tar.bz2 -https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.3-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/py-opencv-4.9.0-qt6_py39h50579d7_612.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.13.0-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.16.3-hd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/win-64/opencv-4.9.0-qt6_py39heec2197_612.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.2.4-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.25.4-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.16.3-hd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.1.5-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/notebook-7.1.2-pyhd8ed1ab_0.conda -https://conda.anaconda.org/conda-forge/noarch/jupyter-1.0.0-pyhd8ed1ab_10.conda diff --git a/setup.cfg b/setup.cfg index ba860c3..22f6ff7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,6 +42,20 @@ console_scripts = dev = bumpver==2021.1114 dunamai==1.7.0 +docs = + ipython==7.22.0 + jinja2==3.0.3 + jupyter==1.0.0 + myst-parser==0.15.2 + nbsphinx==0.8.2 + sphinx==3.5.3 + sphinx-autobuild==2021.3.14 + sphinx-copybutton==0.3.1 + sphinx-markdown-tables + sphinx-panels==0.5.2 + sphinx-rtd-theme==0.5.2 + sphinxcontrib-plantuml==0.20.1 + sphinxcontrib-redoc==1.6.0 pre_commit = pre-commit==3.3.2 pylint==3.2.0 From 1227c5496401327515d078ed47bc864887d3e98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 19 Aug 2024 13:47:17 +0200 Subject: [PATCH 109/114] rebug doc requirements --- Dockerfile.docs | 7 +++--- docs/README.md | 4 ++-- docs/conf.py | 4 ++-- examples/create_and_run_app.py | 41 +++++++++++++++++++++++++--------- setup.cfg | 20 ++++++++--------- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/Dockerfile.docs b/Dockerfile.docs index 9642675..39a87bf 100644 --- a/Dockerfile.docs +++ b/Dockerfile.docs @@ -1,4 +1,4 @@ -FROM python:3.10 +FROM python:3.10-buster RUN apt-get update && apt-get install -y \ pandoc default-jre graphviz \ @@ -8,10 +8,11 @@ RUN apt-get update && apt-get install -y \ latexmk WORKDIR /app -ADD . . +COPY . . RUN python -m pip install --upgrade pip -RUN python -m pip install -e .[docs] + +RUN python -m pip install -e .[docs] --no-cache CMD sphinx-autobuild --host 0.0.0.0 docs/ docs/_build/html diff --git a/docs/README.md b/docs/README.md index 3766da6..a44ef4c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,12 +12,12 @@ This can be done by using docker or installing the development environment direc #### Docker First, build the Docker image by running the following command: ```shell -$ docker build -f Dockerfile.docs -t sphinx-docs . +$ docker build -f Dockerfile.docs -t dsms-sdk-docs . ``` Then, start the program by running: ```shell -$ docker run --rm -v $PWD:/app -p 8000:8000 sphinx-docs +$ docker run -it --rm -v $PWD:/app -p 8000:8000 dsms-sdk-docs ``` #### Linux diff --git a/docs/conf.py b/docs/conf.py index 538bd2a..a21a5cc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,14 +45,14 @@ master_doc = "index" -plantuml = "java -jar /path/to/plantuml.jar" +plantuml = "java -jar lib/plantuml.jar" plantuml_output_format = "svg_img" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. templates_path = ["_templates"] -exclude_patterns = ["build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] def setup(app): diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py index 5aac985..00e7e75 100644 --- a/examples/create_and_run_app.py +++ b/examples/create_and_run_app.py @@ -1,15 +1,18 @@ -"""Example of creating and running apps""" +"""Tutorial for creating and running apps""" import time from dsms import DSMS, AppConfig, KItem +# Connect to DSMS print("\nConnect to DSMS") dsms = DSMS(env="../.env") +# Define app name and item name configname = "testapp2" kitem_name = "test item" +# Define data data = """A,B,C 1.2,1.3,1.5 1.7,1.8,1.9 @@ -25,6 +28,7 @@ extension = ".csv" +# Define app parameters parameters = [ {"name": "parser", "value": "csv"}, {"name": "time_series_header_length", "value": 1}, @@ -56,6 +60,7 @@ }, ] +# Define app specification specification = { "apiVersion": "argoproj.io/v1alpha1", "kind": "Workflow", @@ -67,12 +72,14 @@ }, } +# Create app specification print("\nCreate app specification") appspec = AppConfig( name=configname, specification=specification, # this can also be a file path instead of a dict ) +# Create kitem print("\nCreate kitem") item = KItem( name=kitem_name, @@ -90,50 +97,61 @@ avatar={"include_qr": True}, ) +# Commit KItem print("\nCommit KItem") dsms.commit() -print("\nAdd attachment") -# here we are setting the content directly, -# but `item.attachments` can also be a list with a filepath +# Add attachment +# Here we are setting the content directly, +# but item.attachments can also be a list with a filepath # e.g. item.attachments = ["path/to/my/file.csv"] +print("\nAdd attachment") item.attachments = [{"name": "dummy_data.csv", "content": data}] +# Verify that the attachment was added print("\nVerify that the attachment was added") print(item) +# Upload attachment and trigger app print("\nUpload attachment and trigger app") dsms.commit() +# Get dataframe print("\nGet dataframe") print(item.dataframe.StandardForce.convert_to("N")) - -print("\nVerify that the dataframe was deleted") +# Verify that the dataframe was deleted item.dataframe = {} dsms.commit() +print("\nVerify that the dataframe was deleted") print(item) +# Run pipeline manually print("\nRun pipeline manually") job = item.kitem_apps.by_title["data2rdf"].run( attachment_name=item.attachments[0].name, set_token=True, set_host_url=True ) +# See job status print("\nSee job status") print(job.status) print("\n See job logs:") print(job.logs) -print("\nGet dataframe") +# Get dataframe again +print("\nGet dataframe again") print(item.dataframe.StandardForce.convert_to("N")) +# Delete dataframe print("\nDelete dataframe") item.dataframe = {} dsms.commit() -print("\nVerify that the dataframe was deleted") +# Verify that the dataframe was deleted again +print("\nVerify that the dataframe was deleted again") print(item) +# Run pipeline manually in the background print("\nRun pipeline manually in the background") job = item.kitem_apps.by_title["data2rdf"].run( attachment_name=item.attachments[0].name, @@ -142,7 +160,7 @@ wait=False, ) - +# Monitor job status print("\nMonitor job status") while True: time.sleep(1) @@ -153,12 +171,15 @@ if job.status.phase != "Running": break +# Reload item print("\nReload item") item.refresh() -print("\nGet dataframe") +# Get dataframe again +print("\nGet dataframe again") print(item.dataframe.StandardForce.convert_to("N")) +# Cleanup print("\nCleanup") del dsms[item] del dsms[appspec] diff --git a/setup.cfg b/setup.cfg index 22f6ff7..723099c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,18 +43,16 @@ dev = bumpver==2021.1114 dunamai==1.7.0 docs = - ipython==7.22.0 - jinja2==3.0.3 + ipython==8.26.0 jupyter==1.0.0 - myst-parser==0.15.2 - nbsphinx==0.8.2 - sphinx==3.5.3 - sphinx-autobuild==2021.3.14 - sphinx-copybutton==0.3.1 - sphinx-markdown-tables - sphinx-panels==0.5.2 - sphinx-rtd-theme==0.5.2 - sphinxcontrib-plantuml==0.20.1 + myst-parser==4.0.0 + nbsphinx==0.9.5 + sphinx-autobuild==2024.4.16 + sphinx-copybutton==0.5.2 + sphinx-markdown-tables==0.0.17 + sphinx-panels==0.4.1 + sphinx-rtd-theme==2.0.0 + sphinxcontrib-plantuml==0.30 sphinxcontrib-redoc==1.6.0 pre_commit = pre-commit==3.3.2 From 0325c3dfe8d2cda51670dc1b6d8585bb908f8682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 19 Aug 2024 13:56:36 +0200 Subject: [PATCH 110/114] update sphinx setup --- docs/conf.py | 5 ++++- setup.cfg | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a21a5cc..9cccd31 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,6 +44,7 @@ ] master_doc = "index" +myst_enable_extensions = ["colon_fence"] plantuml = "java -jar lib/plantuml.jar" plantuml_output_format = "svg_img" @@ -53,6 +54,7 @@ # This pattern also affects html_static_path and html_extra_path. templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] +html_static_path = ["assets"] def setup(app): @@ -64,7 +66,7 @@ def setup(app): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" +html_theme = "sphinx_book_theme" html_logo = "assets/images/DSMS_logo.png" # Add any paths that contain custom static files (such as style sheets) here, @@ -80,6 +82,7 @@ def setup(app): } nbsphinx_allow_errors = True +nbsphinx_execute = "never" # -- Options for LaTeX output ------------------------------------------------- latex_documents = [ diff --git a/setup.cfg b/setup.cfg index 723099c..082febd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,10 +48,10 @@ docs = myst-parser==4.0.0 nbsphinx==0.9.5 sphinx-autobuild==2024.4.16 + sphinx-book-theme==1.1.3 sphinx-copybutton==0.5.2 sphinx-markdown-tables==0.0.17 sphinx-panels==0.4.1 - sphinx-rtd-theme==2.0.0 sphinxcontrib-plantuml==0.30 sphinxcontrib-redoc==1.6.0 pre_commit = From 98556e38de507dc7d4f9a88eb14f1232dd4aba19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 19 Aug 2024 16:47:02 +0200 Subject: [PATCH 111/114] update docs --- docs/conf.py | 1 - docs/{ => dsms_sdk}/dsms_config_schema.md | 22 ++++---- docs/{ => dsms_sdk}/dsms_kitem_schema.md | 53 +++++++++---------- docs/{ => dsms_sdk}/dsms_sdk.md | 17 ++---- .../tutorials/1_introduction.ipynb | 7 ++- .../{ => dsms_sdk}/tutorials/2_creation.ipynb | 4 +- .../{ => dsms_sdk}/tutorials/3_updation.ipynb | 5 +- .../{ => dsms_sdk}/tutorials/4_deletion.ipynb | 6 ++- docs/{ => dsms_sdk}/tutorials/5_search.ipynb | 4 +- .../{ => dsms_sdk}/tutorials/6_app_HDF5.ipynb | 5 +- docs/index.md | 53 ++++++++----------- 11 files changed, 78 insertions(+), 99 deletions(-) rename docs/{ => dsms_sdk}/dsms_config_schema.md (86%) rename docs/{ => dsms_sdk}/dsms_kitem_schema.md (94%) rename docs/{ => dsms_sdk}/dsms_sdk.md (94%) rename docs/{ => dsms_sdk}/tutorials/1_introduction.ipynb (93%) rename docs/{ => dsms_sdk}/tutorials/2_creation.ipynb (96%) rename docs/{ => dsms_sdk}/tutorials/3_updation.ipynb (97%) rename docs/{ => dsms_sdk}/tutorials/4_deletion.ipynb (95%) rename docs/{ => dsms_sdk}/tutorials/5_search.ipynb (98%) rename docs/{ => dsms_sdk}/tutorials/6_app_HDF5.ipynb (95%) diff --git a/docs/conf.py b/docs/conf.py index 9cccd31..ff9c5e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -77,7 +77,6 @@ def setup(app): html_static_path = ["_static"] templates_path = ["_templates"] html_theme_options = { - "style_nav_header_background": "#4472c4", "use_download_button": True, } diff --git a/docs/dsms_config_schema.md b/docs/dsms_sdk/dsms_config_schema.md similarity index 86% rename from docs/dsms_config_schema.md rename to docs/dsms_sdk/dsms_config_schema.md index c0675de..05c59be 100644 --- a/docs/dsms_config_schema.md +++ b/docs/dsms_sdk/dsms_config_schema.md @@ -1,11 +1,11 @@ -## 4. DSMS Config Schema +# DSMS Config Schema The `Configuration` class for the DSMS Python SDK is designed to handle various settings required to connect and interact with a DSMS instance. This documentation provides a detailed overview of the configurable properties, their types, defaults, and descriptions. This section describes the configuration properties for the DSMS Python SDK. -### Configuration Object Properties +## Configuration Fields | Field Name | Description | Type | Default | Property Namespace | Required/Optional | |:----------------:|:--------------------------------------------------------------------------------------------:|:--------------------:|:--------------------:|:------------------:|:-----------------:| @@ -20,17 +20,17 @@ This section describes the configuration properties for the DSMS Python SDK. | datetime_format | Datetime format used in the DSMS instance. | str | “%Y-%m-%dT%H:%M:%S.%f” | `datetime_format` | Optional | | kitem_repo | Repository of the triplestore for KItems in the DSMS | str | `knowledge` | `kitem_repo` | Optional | -#### Example Usage +## Example Usage ```python -from dsms.configuration import Configuration -from pydantic import SecretStr +from dsms import DSMS -config = Configuration( + +config = DSMS( host_url="https://dsms.example.com", request_timeout=30, ssl_verify=True, - username=SecretStr("your_username"), - password=SecretStr("your_password"), + username="****", + password="****", token=None, ping_dsms=True, individual_slugs=True, @@ -45,9 +45,5 @@ config = Configuration( hide_properties={"external_links"} ) -print(config) +print(dsms.config) ``` - - -> **Reminder Tip :** -> As discussed in previous section in DSMS SDK section (refer to the [Accessing DSMS Core](dsms_sdk.md#accessing-dsms-core)) for accessing DSMS core, you either need to pass during initialization : `token` or `username + password`. diff --git a/docs/dsms_kitem_schema.md b/docs/dsms_sdk/dsms_kitem_schema.md similarity index 94% rename from docs/dsms_kitem_schema.md rename to docs/dsms_sdk/dsms_kitem_schema.md index 3e55513..a638742 100644 --- a/docs/dsms_kitem_schema.md +++ b/docs/dsms_sdk/dsms_kitem_schema.md @@ -1,12 +1,11 @@ -## 3. DSMS KItem Schema +# DSMS KItem Schema -A Kitem has several properties which enable it to handle data effectively. This section briefly describes the properties a Kitem can consist of, or in simple words, the schema of a KItem. +A Kitem has several properties (pydantic [`Fields`](https://docs.pydantic.dev/latest/concepts/fields/), simply referenced as `Fields` in the following) which enable it to handle data effectively. This section briefly describes the properties a Kitem can consist of, or in simple words, the schema of a KItem. The schema contains complex types and references, indicating an advanced usage scenario where various objects (like KItems and their properties) are interconnected. It also includes customizations like optional and default values, arrays of references, and conditional formats (e.g., UUID formats). -The KItem object has the following properties -### Kitem Object Properties +## KItem Fields ![kitem_schema_uml](assets/images/UML_KItem_schema.jpg) @@ -31,7 +30,7 @@ The KItem object has the following properties | Linked KItems | List of other KItems linked to this KItem | List[Union[[LinkedKItem](#linkedkitem-properties), "KItem"]] | `None` | `linked_kitems` | Optional | | User Groups | User groups with access to this KItem | List[[UserGroup](#usergroup-properties)] | `[ ]` | `user_groups` | Optional | -#### Example Usage +### Example Usage ```python item = KItem( @@ -52,31 +51,31 @@ item = KItem( ) ``` -### Summary Properties +## Summary | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Text | Summary | string | `None` | `text` | Required | | Author | Author of the summary | string | `None` | `author` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.summary = {"text": "This is a summary", "author": "John Doe"} ``` -### App Properties +## App Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Executable | Name of the executable | string | `None` | `executable` | Required | | Description | Description of the application | string | `None` | `description` |Required | -#### Example Usage +### Example Usage ```python sample_kitem.summary = {"executable": "unit_conversion.py", "description": "converting input to different units"} ``` -### Annotation Properties +## Annotation Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| @@ -84,34 +83,34 @@ sample_kitem.summary = {"executable": "unit_conversion.py", "description": "conv | Name | Name of the annotation | string | `None` | `name` | Required | | Namespace | Namespace of the annotation | string | `None` | `namespace` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.annotation = {"iri": "1238.py", "name": "","namespace":""} ``` -### Affiliation Properties +## Affiliation Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Name | Name of the affiliation | string | `None` | `name` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.affiliation = {"name": "Research BAC"} ``` -### Author Properties +## Author Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| | User Id | ID of the DSMS User | string (UUID) | `None` | `user_id` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.author = {"user_id": "1238"} ``` -### ContactInfo Properties +## ContactInfo Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| @@ -119,61 +118,61 @@ sample_kitem.author = {"user_id": "1238"} | Name | Name of the contact person | string | `None` | `name` | Required | | User Id | User ID of the contact person | string (UUID) | `None` | `user_id` | Optional | -#### Example Usage +### Example Usage ```python sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3"} ``` -### ExternalLink Properties +## ExternalLink Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------------------------:|:-------:|:------------------:|:-----------------:| | Label | Label of the external link | string | `None` | `label` | Required | | Url | URL of the external link | string , format: URI, minLength: 1 | `None` | `url` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.externallink = {"label": "project link","url": "www.projectmachine01.com"} ``` -### Attachment Properties +## Attachment Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Name | File name of the attachment | string | `None` | `name` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.attachment = {"strength.csv": "strength test results data"} ``` -### Column Properties +## Column Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Name | File name of the attachment | string | `None` | `name` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3"} ``` -### LinkedKItem Properties +## LinkedKItem Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| | Id | ID of the KItem to be linked | string (UUID) | `None` | `id` | Required | | Source Id | Source Id of the KItem which has been linked | string (UUID) | `None` | `source_id` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} ``` -### UserGroup Properties +## UserGroup Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| @@ -181,7 +180,7 @@ sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} | Group Id | ID of the user group | string | `None` | `group_id` | Required | | Name | Name of the user group | string | `None` | `name` | Required | -#### Example Usage +### Example Usage ```python sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} ``` diff --git a/docs/dsms_sdk.md b/docs/dsms_sdk/dsms_sdk.md similarity index 94% rename from docs/dsms_sdk.md rename to docs/dsms_sdk/dsms_sdk.md index 1b4822a..fadff85 100644 --- a/docs/dsms_sdk.md +++ b/docs/dsms_sdk/dsms_sdk.md @@ -68,9 +68,9 @@ cd dsms-python-sdk pip install . ``` -### Accessing DSMS Core +### Connecting to DSMS -You need to authenticate yourself to connect with dsms-core using dsms-python-sdk which can be done by following one of the below given steps: +You need to authenticate yourself to connect with dsms using the `dsms-sdk` Python package which can be done by following one of the below given steps: 1. **Pick the DSMS host of your choice.** @@ -89,7 +89,7 @@ You need to authenticate yourself to connect with dsms-core using dsms-python-sd Directly pass your credentials (stored in a .env file) as keyword arguments when initializing the SDK. ```python - from dsms_sdk import DSMS + from dsms import DSMS dsms = DSMS(env="../.env") ``` @@ -109,7 +109,7 @@ You need to authenticate yourself to connect with dsms-core using dsms-python-sd ```python import getpass - from dsms_sdk import DSMS + from dsms import DSMS dsms_host = "https://stahldigital.materials-data.space" dsms_username = input("Enter your username: ") dsms_password = getpass.getpass("Enter your password: ") @@ -151,13 +151,4 @@ Now you are ready to use dsms-sdk. Do check out the tutorials section to try out The next sections covers about the schema of fundamental classes crucial for users to know about DSMS when using the platform. Below given explains about the Schema of `KItem` and its associated properties in DSMS. - - -```{include} dsms_kitem_schema.md -``` - Now the next section gives a brief explanation about the Schema of `Config` class of DSMS and its associated properties in DSMS. - - -```{include} dsms_config_schema.md -``` diff --git a/docs/tutorials/1_introduction.ipynb b/docs/dsms_sdk/tutorials/1_introduction.ipynb similarity index 93% rename from docs/tutorials/1_introduction.ipynb rename to docs/dsms_sdk/tutorials/1_introduction.ipynb index 32895d0..ceee922 100644 --- a/docs/tutorials/1_introduction.ipynb +++ b/docs/dsms_sdk/tutorials/1_introduction.ipynb @@ -4,10 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial 1: Introduction to the DSMS-SDK\n", + "# 1. Connecting with the SDK to DSMS\n", "\n", - "In this tutorial we see the overview on how to setup and basic use DSMS-SDK\n", - "\n" + "In this tutorial we see the overview on how to setup and basic use DSMS-SDK\n" ] }, { @@ -15,7 +14,7 @@ "metadata": {}, "source": [ "### 1.1. Setting up\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] diff --git a/docs/tutorials/2_creation.ipynb b/docs/dsms_sdk/tutorials/2_creation.ipynb similarity index 96% rename from docs/tutorials/2_creation.ipynb rename to docs/dsms_sdk/tutorials/2_creation.ipynb index 75924a2..01f7e7e 100644 --- a/docs/tutorials/2_creation.ipynb +++ b/docs/dsms_sdk/tutorials/2_creation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial 2: Create Kitems\n", + "# 2. Create KItems with the SDK\n", "\n", "In this tutorial we see how to create new Kitems." ] @@ -14,7 +14,7 @@ "metadata": {}, "source": [ "### 2.1. Setting up\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] diff --git a/docs/tutorials/3_updation.ipynb b/docs/dsms_sdk/tutorials/3_updation.ipynb similarity index 97% rename from docs/tutorials/3_updation.ipynb rename to docs/dsms_sdk/tutorials/3_updation.ipynb index 1e65563..a0d82f1 100644 --- a/docs/tutorials/3_updation.ipynb +++ b/docs/dsms_sdk/tutorials/3_updation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial 3: Updating KItems\n", + "# 3. Updating KItems with the SDK\n", "\n", "In this tutorial we see how to update existing Kitems." ] @@ -14,7 +14,8 @@ "metadata": {}, "source": [ "### 3.1. Setting up\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", + "\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", "\n", "Now let us import the needed classes and functions for this tutorial." diff --git a/docs/tutorials/4_deletion.ipynb b/docs/dsms_sdk/tutorials/4_deletion.ipynb similarity index 95% rename from docs/tutorials/4_deletion.ipynb rename to docs/dsms_sdk/tutorials/4_deletion.ipynb index 603091c..8d25ea3 100644 --- a/docs/tutorials/4_deletion.ipynb +++ b/docs/dsms_sdk/tutorials/4_deletion.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial 4: Deleting KItems\n", + "# 4. Deleting KItems with the SDK\n", "\n", "In this tutorial we see how to delete new Kitems and their properties." ] @@ -15,7 +15,9 @@ "source": [ "### 4.1. Setting up\n", "\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", + "\n", + "Now let us import the needed classes and functions for this tutorial.\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] diff --git a/docs/tutorials/5_search.ipynb b/docs/dsms_sdk/tutorials/5_search.ipynb similarity index 98% rename from docs/tutorials/5_search.ipynb rename to docs/dsms_sdk/tutorials/5_search.ipynb index 047dc18..5058748 100644 --- a/docs/tutorials/5_search.ipynb +++ b/docs/dsms_sdk/tutorials/5_search.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial 5: Searching Kitems\n", + "# 5. Searching KItems with the SDK\n", "\n", "In this tutorial we see how to search existing Kitems" ] @@ -14,8 +14,8 @@ "metadata": {}, "source": [ "### 5.1. Setting up\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] diff --git a/docs/tutorials/6_app_HDF5.ipynb b/docs/dsms_sdk/tutorials/6_app_HDF5.ipynb similarity index 95% rename from docs/tutorials/6_app_HDF5.ipynb rename to docs/dsms_sdk/tutorials/6_app_HDF5.ipynb index b20c513..1ba2299 100644 --- a/docs/tutorials/6_app_HDF5.ipynb +++ b/docs/dsms_sdk/tutorials/6_app_HDF5.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tutorial 6: App availability and HDF5\n", + "# 6. App availability and HDF5\n", "\n", "In this tutorial we see how to search existing Kitems" ] @@ -15,7 +15,8 @@ "source": [ "### 6.1: Setting up\n", "\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Accessing DSMS Core via SDK](../dsms_sdk.md#accessing-dsms-core))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", + "\n", "\n", "Now let us import the needed classes and functions for this tutorial." ] diff --git a/docs/index.md b/docs/index.md index f3d0975..d500a3a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,24 +26,6 @@ Introduction to DSMS :text: Basics of DSMS-SDK :classes: btn-outline-primary stretched-link ---- -**DSMS KITEM SCHEMA** - -Overview of DSMS KItem Schema - -```{link-button} dsms_kitem_schema.html -:text: DSMS KItem Schema -:classes: btn-outline-primary stretched-link - ---- -**DSMS CONFIG SCHEMA** - -Overview of DSMS Config Schema - -```{link-button} dsms_config_schema.html -:text: DSMS Config Schema -:classes: btn-outline-primary stretched-link - --- **Tutorials** @@ -61,23 +43,32 @@ Feel free to report any issues/missing information so we can take a look into it ```{toctree} :hidden: true -:caption: Table of contents -:maxdepth: 1 +:caption: Introduction +:maxdepth: 4 +:glob: + +The Data Space Management System +``` + + +```{toctree} +:hidden: true +:caption: DSMS Python SDK +:maxdepth: 4 +:glob: + +dsms_sdk/dsms_sdk +dsms_sdk/dsms_config_schema +dsms_sdk/dsms_kitem_schema -dsms -dsms_sdk -Tutorials ``` ```{toctree} :hidden: true :caption: Tutorials -:maxdepth: 2 - -1. Introduction -2. Creation -3. Updation -4. Deletion -5. Search -6. App Availability +:maxdepth: 4 +:glob: + +dsms_sdk/tutorials/* + ``` From 0bebb1dd59e8da4a8bf772872b6af15d88bacde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Mon, 19 Aug 2024 22:31:19 +0200 Subject: [PATCH 112/114] update docs --- .vscode/settings.json | 3 - docs/dsms_sdk/dsms_config_schema.md | 31 +- docs/dsms_sdk/dsms_kitem_schema.md | 116 +- docs/dsms_sdk/dsms_sdk.md | 12 +- docs/dsms_sdk/tutorials/1_introduction.ipynb | 46 +- docs/dsms_sdk/tutorials/2_creation.ipynb | 185 ++- docs/dsms_sdk/tutorials/3_updation.ipynb | 154 ++- docs/dsms_sdk/tutorials/4_deletion.ipynb | 228 +++- docs/dsms_sdk/tutorials/5_search.ipynb | 294 +++- docs/dsms_sdk/tutorials/6_app_HDF5.ipynb | 152 --- docs/dsms_sdk/tutorials/6_apps.ipynb | 1073 +++++++++++++++ docs/dsms_sdk/tutorials/testfile.txt | 1 + dsms/knowledge/kitem.py | 2 +- examples/unit_conversion.ipynb | 1265 ------------------ 14 files changed, 1996 insertions(+), 1566 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 docs/dsms_sdk/tutorials/6_app_HDF5.ipynb create mode 100644 docs/dsms_sdk/tutorials/6_apps.ipynb create mode 100755 docs/dsms_sdk/tutorials/testfile.txt delete mode 100644 examples/unit_conversion.ipynb diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3545a38..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "livePreview.defaultPreviewPath": "/docs/src/build/html/index.html" -} diff --git a/docs/dsms_sdk/dsms_config_schema.md b/docs/dsms_sdk/dsms_config_schema.md index 05c59be..c0907b7 100644 --- a/docs/dsms_sdk/dsms_config_schema.md +++ b/docs/dsms_sdk/dsms_config_schema.md @@ -9,16 +9,24 @@ This section describes the configuration properties for the DSMS Python SDK. | Field Name | Description | Type | Default | Property Namespace | Required/Optional | |:----------------:|:--------------------------------------------------------------------------------------------:|:--------------------:|:--------------------:|:------------------:|:-----------------:| -| host_url | URL of the DSMS instance to connect. | AnyUrl | Not Applicable | `host_url` | Required | -| request_timeout | Timeout in seconds until the request to the DSMS is timed out. | int | `30` | `request_timeout` | Optional | -| ssl_verify | Whether the SSL of the DSMS shall be verified during connection. | bool | `True` | `ssl_verify` | Optional | -| username | User name for connecting to the DSMS instance | Optional[SecretStr] | `None` | `username` | Optional | -| password | Password for connecting to the DSMS instance | Optional[SecretStr] | `None` | `password` | Optional | -| token | JWT bearer token for connecting to the DSMS instance | Optional[SecretStr] | `None` | `token` | Optional | -| ping_dsms | Check whether the host is a DSMS instance or not. | bool | `True` | `ping_dsms` | Optional | -| encoding | General encoding to be used for reading/writing serializations. | str | “utf-8” | `encoding` | Optional | -| datetime_format | Datetime format used in the DSMS instance. | str | “%Y-%m-%dT%H:%M:%S.%f” | `datetime_format` | Optional | -| kitem_repo | Repository of the triplestore for KItems in the DSMS | str | `knowledge` | `kitem_repo` | Optional | +| Host URL | URL of the DSMS instance to connect. | AnyUrl | Not Applicable | `host_url` | Required | +| Request timeout | Timeout in seconds until the request to the DSMS is timed out. | int | `120` | `request_timeout` | Optional | +| SSL verify | Whether the SSL of the DSMS shall be verified during connection. | bool | `True` | `ssl_verify` | Optional | +| Username | User name for connecting to the DSMS instance | Optional[SecretStr] | `None` | `username` | Optional | +| Password | Password for connecting to the DSMS instance | Optional[SecretStr] | `None` | `password` | Optional | +| Token | JWT bearer token for connecting to the DSMS instance | Optional[SecretStr] | `None` | `token` | Optional | +| Ping DSMS | Check whether the host is a DSMS instance or not. | bool | `True` | `ping_dsms` | Optional | +| Encoding | General encoding to be used for reading/writing serializations. | str | “utf-8” | `encoding` | Optional | +| Datetime format | Datetime format used in the DSMS instance. | str | “%Y-%m-%dT%H:%M:%S.%f” | `datetime_format` | Optional | +| KItem repository | Repository of the triplestore for KItems in the DSMS | str | `knowledge` | `kitem_repo` | Optional | +| SPARQL Object for units | Class and Module specification in order to retrieve the units. | str | `dsms.`
    `knowledge.`
    `semantics.`
    `units.`
    `sparql:`
    `UnitSparqlQuery` | `units_sparql_object`| Optional | +| Individual Slugs | When set to `True`, the slugs of the KItems will receive the first few characters of the KItem-id, when the slug is derived automatically from the KItem-name. | bool | `True` | `individual_slugs` | Optional | +| Display units | Whether the custom properties or the dataframe columns shall directly reveal their unit when printed. WARNING: This might lead to performance issues. | bool | `False` | `display_units` | Optional | +| Autocomplete units | When a unit is fetched but does not hold a symbol next to its URI, it shall be fetched from the respective ontology (which is general side effect from the `units_sparq_object`).
    WARNING: This might lead to performance issues. | bool | `True` | `autocomplete_units` | Optional | +| QUDT units | URI of the QUDT unit ontology | str | `http://qudt.org/2.1/vocab/unit` | `qudt_units` | Optional | +| QUDT Quantity Kinds | URI of the QUDT quantity kind ontology | str | `http://qudt.org/vocab/quantitykind/` | `qudt_quantity_kinds` | Optional | +| Hide properties | Properties to hide while printing, e.g {'external_links'} | Set[str] | `{}` | `hide_properties` | Optional | +| Log level | Logging level | str | None | `log_level` | Optional | ## Example Usage ```python @@ -42,7 +50,8 @@ config = DSMS( qudt_units="http://qudt.org/2.1/vocab/unit", qudt_quantity_kinds="http://qudt.org/vocab/quantitykind/", units_sparql_object="dsms.knowledge.semantics.units.sparql:UnitSparqlQuery", - hide_properties={"external_links"} + hide_properties={"external_links"}, + log_level="INFO", ) print(dsms.config) diff --git a/docs/dsms_sdk/dsms_kitem_schema.md b/docs/dsms_sdk/dsms_kitem_schema.md index a638742..f1c21c9 100644 --- a/docs/dsms_sdk/dsms_kitem_schema.md +++ b/docs/dsms_sdk/dsms_kitem_schema.md @@ -7,7 +7,7 @@ The schema contains complex types and references, indicating an advanced usage s ## KItem Fields -![kitem_schema_uml](assets/images/UML_KItem_schema.jpg) +![kitem_schema_uml](../assets/images/UML_KItem_schema.jpg) | Field Name | Description | Type | Default | Property Namespace | Required / Optional | |:-----------------:|:--------------------------------------------------------------------------------------------------------:|:-------------------------------------------------:|:--------:|:------------------:|:-----------------:| @@ -18,17 +18,16 @@ The schema contains complex types and references, indicating an advanced usage s | Updated At | Timestamp of when the KItem was updated. | Union[string, datetime] | `None` | `updated_at` | Automatically generated | | Avatar Exists | Whether the KItem holds an avatar or not. | boolean | `False` | `avatar_exists` | Automatically generated | | Custom Properties | A set of custom properties related to the KItem. | Any | `{}` | `custom_properties`| Optional | -| Summary | A brief human-readable summary of the KItem | Union[string, [Summary](#summary-properties)] | `None` | `summary` | Optional | -| KItem Apps | A list of applications associated with the KItem | List[[App](#app-properties)] | `[ ]` | `kitem_apps` | Optional | -| Annotations | A list of annotations related to the KItem | List[[Annotation](#annotation-properties)] | `[ ]` | `annotations` | Optional | -| Affiliations | A list of affiliations associated with the KItem | List[[Affiliation](#affiliation-properties)] | `[ ]` | `affiliations` | Optional | -| Authors | A list of authors related to the KItem | List[Union[[Author](#author-properties), string]] | `[ ]` | `authors` | Optional | -| Contacts | Contact information related to the KItem | List[[ContactInfo](#contactinfo-properties)] | `[ ]` | `contacts` | Optional | -| External Links | A list of external links related to the KItem | List[[ExternalLink](#externallink-properties)] | `[ ]` | `external_links` | Optional | -| Attachments | A list of file attachments associated with the KItem | List [ Union [[Attachment ](#attachment-properties)], string] | `[ ]` | `attachments` | Optional | -| Hdf5 | HDF5 data structure associated with the KItem | Union[List[[Column](#column-properties)], pd.DataFrame, Dictionary[string, Union[List, Dictionary]]] | `None` | `hdf5` | Optional | -| Linked KItems | List of other KItems linked to this KItem | List[Union[[LinkedKItem](#linkedkitem-properties), "KItem"]] | `None` | `linked_kitems` | Optional | -| User Groups | User groups with access to this KItem | List[[UserGroup](#usergroup-properties)] | `[ ]` | `user_groups` | Optional | +| Summary | A brief human-readable summary of the KItem | string] | `None` | `summary` | Optional | +| KItem Apps | A list of applications associated with the KItem | List[[App](#app-fields)] | `[ ]` | `kitem_apps` | Optional | +| Annotations | A list of annotations related to the KItem | List[[Annotation](#annotation-fields)] | `[ ]` | `annotations` | Optional | +| Affiliations | A list of affiliations associated with the KItem | List[[Affiliation](#affiliation-fields)] | `[ ]` | `affiliations` | Optional | +| Contacts | Contact information related to the KItem | List[[ContactInfo](#contactinfo-fields)] | `[ ]` | `contacts` | Optional | +| External Links | A list of external links related to the KItem | List[[ExternalLink](#externallink-fields)] | `[ ]` | `external_links` | Optional | +| Attachments | A list of file attachments associated with the KItem | List [ Union [[Attachment ](#attachment-fields)], string] | `[ ]` | `attachments` | Optional | +| Dataframe | Dataframe associated with the KItem, e.g. a time series | Union[List[[Column](#column-fields)], pd.DataFrame, Dictionary[string, Union[List, Dictionary]]] | `None` | `dataframe` | Optional | +| Linked KItems | List of other KItems linked to this KItem | List[Union[[LinkedKItem](#linkedkitem-fields), "KItem"]] | `None` | `linked_kitems` | Optional | +| User Groups | User groups with access to this KItem | List[[UserGroup](#usergroup-fields)] | `[ ]` | `user_groups` | Optional | ### Example Usage ```python @@ -38,11 +37,10 @@ item = KItem( slug="1234", ktype_id="Testing Machine", custom_properties={"location": "Room01", "max_force": "100Pa"}, - summary={"text": "This is a summary", "author": "John Doe"}, - kitem_apps=[{"executable": "tensile_analysis.py", "description": "analysis the tensile strength from machine data"}], - annotations=[{"iri": "http://example.org/sample_kitem/annotation"}], - affiliations=[{"name": "Fraunhofer IWM"}], - authors=[{"user_id": "Tom Brown"}], + summary="This is a summary", + kitem_apps=[{"executable": "my_analysis", "title": "Analysis", "description": "analysis the tensile strength from machine data"}], + annotations=["http://example.org/sample_kitem/annotation"], + affiliations=[{"name": "Institute ABC"}], contacts=[{"name": "John Doe", "email": "john.doe@example.com"}], external_links=[{"label": "Project Website", "url": "https://example.com"}], attachments=["research_data.csv"], @@ -51,28 +49,22 @@ item = KItem( ) ``` -## Summary - -| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | -|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| -| Text | Summary | string | `None` | `text` | Required | -| Author | Author of the summary | string | `None` | `author` | Required | - -### Example Usage -```python -sample_kitem.summary = {"text": "This is a summary", "author": "John Doe"} -``` ## App Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Executable | Name of the executable | string | `None` | `executable` | Required | +| Title | Title of the application | string | `None` | `title` | Required | | Description | Description of the application | string | `None` | `description` |Required | ### Example Usage ```python -sample_kitem.summary = {"executable": "unit_conversion.py", "description": "converting input to different units"} +sample_kitem.kitem_apps = { + "executable": "my_application", + "title": "My Application", + "description": "My Application for analysis.", +} ``` ## Annotation Fields @@ -85,7 +77,18 @@ sample_kitem.summary = {"executable": "unit_conversion.py", "description": "conv ### Example Usage ```python -sample_kitem.annotation = {"iri": "1238.py", "name": "","namespace":""} +sample_kitem.annotations = [ + "http://example.org/TensileTest" +] +``` +```python +sample_kitem.annotations = [ + { + "iri":"http://example.org/TensileTest", + "name": "TensileTest", + "namespace": "http://example.org" + } +] ``` ## Affiliation Fields @@ -96,19 +99,9 @@ sample_kitem.annotation = {"iri": "1238.py", "name": "","namespace":""} ### Example Usage ```python -sample_kitem.affiliation = {"name": "Research BAC"} +sample_kitem.affiliations = {"name": "Research BAC"} ``` -## Author Fields - -| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | -|:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| -| User Id | ID of the DSMS User | string (UUID) | `None` | `user_id` | Required | - -### Example Usage -```python -sample_kitem.author = {"user_id": "1238"} -``` ## ContactInfo Fields @@ -120,7 +113,10 @@ sample_kitem.author = {"user_id": "1238"} ### Example Usage ```python -sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3"} +sample_kitem.contacts = { + "email": "research.abc@gmail.com", + "name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3" +} ``` ## ExternalLink Fields @@ -132,7 +128,10 @@ sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01 ### Example Usage ```python -sample_kitem.externallink = {"label": "project link","url": "www.projectmachine01.com"} +sample_kitem.external_links = [{ + "label": "project link", + "url": "www.projectmachine01.com" +}] ``` @@ -141,12 +140,21 @@ sample_kitem.externallink = {"label": "project link","url": "www.projectmachine0 | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| | Name | File name of the attachment | string | `None` | `name` | Required | +| Content | Content of the attachment | string | `None` | `content` | Optional | ### Example Usage ```python -sample_kitem.attachment = {"strength.csv": "strength test results data"} +sample_kitem.attachments = ["research_data.csv"] ``` +```python +sample_kitem.attachments = [ + { + "name": "research_data.csv", + "content": "A,B,C\n1,2,3\n4,5,6" + } +] +``` ## Column Fields @@ -156,7 +164,11 @@ sample_kitem.attachment = {"strength.csv": "strength test results data"} ### Example Usage ```python -sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3"} +sample_kitem.dataframe = { + "A": [1, 4], + "B": [2, 5], + "C": [3, 6] +} ``` @@ -169,7 +181,12 @@ sample_kitem.contactinfo = {"email": "research.abc@gmail.com","name": "project01 ### Example Usage ```python -sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} +sample_kitem.linked_kitems = [ + { + "id": "33305", + "source_id": "22205" + } +] ``` ## UserGroup Fields @@ -182,5 +199,10 @@ sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} ### Example Usage ```python -sample_kitem.linkedKItem = {"id": "33305","source_id": "22205"} +sample_kitem.user_groups = [ + { + "group_id": "33305", + "name": "22205" + } +] ``` diff --git a/docs/dsms_sdk/dsms_sdk.md b/docs/dsms_sdk/dsms_sdk.md index fadff85..db1fe36 100644 --- a/docs/dsms_sdk/dsms_sdk.md +++ b/docs/dsms_sdk/dsms_sdk.md @@ -33,7 +33,7 @@ The SDK functionalities are listed below: Click on the link to go to the Github repository of the Python based DSMS-SDK : [Git repo](https://github.com/MI-FraunhoferIWM/dsms-python-sdk) -![dsms-sdk](assets/images/DSMS_SDK.jpg) +![dsms-sdk](../assets/images/DSMS_SDK.jpg) ## 2. Installation @@ -123,23 +123,23 @@ You need to authenticate yourself to connect with dsms using the `dsms-sdk` Pyth Step 1: Login into the selected dataspace instance of your choice. - ![copy_token_1](assets/images/copy_token_1.jpg) + ![copy_token_1](../assets/images/copy_token_1.jpg) Step 2: Enter your credentials - ![copy_token_2](assets/images/copy_token_2.jpg) + ![copy_token_2](../assets/images/copy_token_2.jpg) Step 3: After logging in you will land up on the home page. Now proceed to the my profile option - ![copy_token_3](assets/images/copy_token_3.jpg) + ![copy_token_3](../assets/images/copy_token_3.jpg) Step 4: The my profile option should look something like below. Now click on Advanced. - ![copy_token_4](assets/images/copy_token_4.jpg) + ![copy_token_4](../assets/images/copy_token_4.jpg) Step 5: After landing up on Advanced section, click on the copy token to clipboard - ![copy_token_6](assets/images/copy_token_5.jpg) + ![copy_token_6](../assets/images/copy_token_5.jpg) Step 6: Now the login token is in the clipboard. Now paste the copied login token from the frontend in the DSMS_TOKEN attribute of the .env file ``` diff --git a/docs/dsms_sdk/tutorials/1_introduction.ipynb b/docs/dsms_sdk/tutorials/1_introduction.ipynb index ceee922..b9bcb50 100644 --- a/docs/dsms_sdk/tutorials/1_introduction.ipynb +++ b/docs/dsms_sdk/tutorials/1_introduction.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -37,11 +37,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../../../.env\")" + "dsms = DSMS(env=\".env\")" ] }, { @@ -55,14 +55,25 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can see which kind of DSMS-object we own as a user:" + "We can see which kind of DSMS-object we own as a user (in the beginning, we own none):" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "dsms.kitems" ] @@ -80,9 +91,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KTypes.Organization\n", + "KTypes.App\n", + "KTypes.Dataset\n", + "KTypes.DatasetCatalog\n", + "KTypes.Expert\n", + "KTypes.Test\n", + "KTypes.Specimen\n", + "KTypes.Batch\n", + "KTypes.Resource\n", + "KTypes.TestingMachine\n" + ] + } + ], "source": [ "for ktype in dsms.ktypes:\n", " print(ktype)" @@ -105,7 +133,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/2_creation.ipynb b/docs/dsms_sdk/tutorials/2_creation.ipynb index 01f7e7e..63c950d 100644 --- a/docs/dsms_sdk/tutorials/2_creation.ipynb +++ b/docs/dsms_sdk/tutorials/2_creation.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -37,11 +37,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../../../.env\")" + "dsms = DSMS(env=\".env\")" ] }, { @@ -62,16 +62,74 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = KTypes.TestingMachine, \n", + "\n", + "\tin_backend = False, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = None, \n", + "\n", + "\tupdated_at = None, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "item = KItem(\n", " name=\"Machine-1\",\n", " ktype_id=dsms.ktypes.TestingMachine,\n", " custom_properties={\"Producer\": \"TestingLab GmBH\",\n", - " \"Room Number\": \"A404\",\n", - " \"Description\" : \"Bending Test Machine\"\n", + " \"Location\": \"A404\",\n", + " \"Model Number\" : \"Bending Test Machine No 777\"\n", " },\n", ")\n", "\n", @@ -87,9 +145,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'https://bue.materials-data.space/knowledge/testing-machine/machine-1-dd091666'" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "dsms.commit()\n", "item.url" @@ -104,9 +173,71 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "item" ] @@ -120,9 +251,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'Machine-1'" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "item.name" ] @@ -136,9 +278,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dsms.knowledge.kitem.KItem" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "type(item)" ] @@ -172,7 +325,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/3_updation.ipynb b/docs/dsms_sdk/tutorials/3_updation.ipynb index a0d82f1..cfa9245 100644 --- a/docs/dsms_sdk/tutorials/3_updation.ipynb +++ b/docs/dsms_sdk/tutorials/3_updation.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -39,11 +39,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../../../.env\")" + "dsms = DSMS(env=\".env\")" ] }, { @@ -55,11 +55,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Machine-1\n" + ] + } + ], "source": [ - "item = dsms.kitems[0]\n", + "item = dsms.kitems[-1]\n", "print(item.name)" ] }, @@ -83,13 +91,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "item.name = \"Machine-1\"\n", "item.custom_properties.Producer = \"Machinery GmBH\"\n", - "item.attachments.append(\"../README.md\")\n", + "item.attachments.append(\"testfile.txt\")\n", "item.annotations.append(\"www.machinery.org/\")\n", "item.external_links.append(\n", " {\"url\": \"http://machine.org\", \"label\": \"machine-link\"}\n", @@ -101,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -115,6 +123,115 @@ "We can see now that the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have been thrown during the `commit`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see the updates when we print the item:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.machinery.org/,\n", + "\t\t\tname: ,\n", + "\t\t\tnamespace: www.machinery.org,\n", + "\t\t\tdescription: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: testfile.txt\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: machine-team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: machinesupport,\n", + "\t\t\temail: machinesupport@group.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: machine-link,\n", + "\t\t\turl: http://machine.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [\n", + "\t\t{\n", + "\t\t\tname: machinegroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: Machinery GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -124,9 +241,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t\t\t Downloaded file: {\n", + "\t\t\tname: testfile.txt\n", + "\t\t}\n", + "|------------------------------------Beginning of file------------------------------------|\n", + "This is a calibration protocol!\n", + "|---------------------------------------End of file---------------------------------------|\n" + ] + } + ], "source": [ "for file in item.attachments:\n", " download = file.download()\n", @@ -154,7 +284,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/4_deletion.ipynb b/docs/dsms_sdk/tutorials/4_deletion.ipynb index 8d25ea3..e5ecf72 100644 --- a/docs/dsms_sdk/tutorials/4_deletion.ipynb +++ b/docs/dsms_sdk/tutorials/4_deletion.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -40,11 +40,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../../../.env\")" + "dsms = DSMS(env=\".env\")" ] }, { @@ -56,11 +56,101 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.machinery.org/,\n", + "\t\t\tname: ,\n", + "\t\t\tnamespace: www.machinery.org,\n", + "\t\t\tdescription: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: testfile.txt\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: machine-team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: machinesupport,\n", + "\t\t\temail: machinesupport@group.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: machine-link,\n", + "\t\t\turl: http://machine.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [\n", + "\t\t{\n", + "\t\t\tname: machinegroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: Machinery GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n" + ] + } + ], "source": [ - "item = dsms.kitems[0]\n", + "item = dsms.kitems[-1]\n", "print(item)" ] }, @@ -86,9 +176,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{\n", + "\t\t\tname: machinegroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "item.attachments.pop(0)\n", "item.annotations.pop(0)\n", @@ -106,7 +210,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -117,16 +221,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "See the changes:" + "We can delete the custom properties by setting the property to an empty dict:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "item" + "item.custom_properties = {}" ] }, { @@ -138,13 +242,105 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "dsms.commit()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: machine-team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: machinesupport,\n", + "\t\t\temail: machinesupport@group.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: machine-link,\n", + "\t\t\turl: http://machine.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tid: dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\t\tcontent: {}\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -154,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -171,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -206,7 +402,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/5_search.ipynb b/docs/dsms_sdk/tutorials/5_search.ipynb index 5058748..9d54b87 100644 --- a/docs/dsms_sdk/tutorials/5_search.ipynb +++ b/docs/dsms_sdk/tutorials/5_search.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -38,11 +38,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "dsms = DSMS(env=\"../../../.env\")" + "dsms = DSMS(env=\".env\")" ] }, { @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -110,14 +110,14 @@ ")\n", "\n", "item5 = KItem(\n", - " name=\"Fraunhofer BAC\",\n", + " name=\"Research Institute ABC\",\n", " ktype_id=dsms.ktypes.Organization,\n", " linked_kitems=[item1],\n", " annotations=[\n", " {\n", " \"iri\": \"www.researchBACiri.org/foo\",\n", - " \"name\": \"research BAC Institute\",\n", - " \"namespace\": \"research-BAC\",\n", + " \"name\": \"research ABC Institute\",\n", + " \"namespace\": \"research\",\n", " }\n", " ],\n", ")\n", @@ -130,7 +130,7 @@ "metadata": {}, "source": [ "\n", - "####

    Note : Here in this tutorial, we use dsms.search with `limit=1` to make it aesthically precise and maintain easy readability but the user can adjust the variable `limit` as per requirement.

    \n", + "####

    Note : Here in this tutorial, we use dsms.search with `limit=1` to maintain readability but the user can adjust the variable `limit` as per requirement.

    \n", "\n", "\n", "Now, we are apply to search for e.g. kitems of type `TestingMachine`:" @@ -138,11 +138,98 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = 0f49b0ad-2a98-4c10-8440-fd3c37363646, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-0f49b0ad, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [\n", + "\t\t\n", + "\t\t\tid: 075709da-5481-4cb4-a8ee-c18db34ce9d3\n", + "\t\t\tname: Specimen-1\n", + "\t\t\tslug: specimen-1-075709da\n", + "\t\t\tktype_id: specimen\n", + "\t\t\tsummary: None\n", + "\t\t\tavatar_exists: False\n", + "\t\t\tannotations: []\n", + "\t\t\tlinked_kitems: [{\n", + "\t\t\tid: 0f49b0ad-2a98-4c10-8440-fd3c37363646\n", + "\t\t}]\n", + "\t\t\texternal_links: []\n", + "\t\t\tcontacts: []\n", + "\t\t\tauthors: [{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}]\n", + "\t\t\tlinked_affiliations: []\n", + "\t\t\tattachments: []\n", + "\t\t\tuser_groups: []\n", + "\t\t\tcustom_properties: {'sampletype': None, 'sampleinformation': None, 'sampleproductionprocess': None, 'Geometry': 'Cylindrical 150mm x 20mm x 40mm', 'Material': 'Concrete', 'Project ID': 'ConstructionProject2024'}\n", + "\t\t\tcreated_at: 2024-08-19T18:26:00.499708\n", + "\t\t\tupdated_at: 2024-08-19T18:26:00.499708\n", + "\t\t\n", + "\t], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:26:00.247787, \n", + "\n", + "\tupdated_at = 2024-08-19 18:26:00.247787, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tRoom Number: A404, \n", + "\t\tDescription: Bending Test Machine\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n", + "\n", + "\n" + ] + } + ], "source": [ - "dsms.search(ktypes=[dsms.ktypes.TestingMachine], limit=1)" + "for result in dsms.search(ktypes=[dsms.ktypes.TestingMachine], limit=1):\n", + " print(result.hit)\n", + " print(\"\\n\")" ] }, { @@ -154,11 +241,73 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Research Institute ABC, \n", + "\n", + "\tid = 21aa50c3-5ec2-4ac3-aba8-69071a4287e2, \n", + "\n", + "\tktype_id = organization, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = researchinstituteabc-21aa50c3, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:26:00.740761, \n", + "\n", + "\tupdated_at = 2024-08-19 18:26:00.740761, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = None, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n", + "fuzziness: False\n", + "\n", + "\n" + ] + } + ], "source": [ - "dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog], limit=1)" + "for result in dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog], limit=1):\n", + " print(result.hit)\n", + " print(\"fuzziness: \", result.fuzzy)\n", + " print(\"\\n\")\n", + " " ] }, { @@ -170,11 +319,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "dsms.search(query=\"Specimen-1\", ktypes=[dsms.ktypes.Dataset], limit=1)" + "for result in dsms.search(query=\"Specimen-1\", ktypes=[dsms.ktypes.Dataset], limit=1):\n", + " print(result.hit)\n", + " print(\"\\n\")" ] }, { @@ -186,13 +337,105 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Research Institute ABC, \n", + "\n", + "\tid = 84c089e6-feab-4ba6-a934-527594e504eb, \n", + "\n", + "\tktype_id = organization, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = researchinstituteabc-84c089e6, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.researchBACiri.org/foo,\n", + "\t\t\tname: research ABC Institute,\n", + "\t\t\tnamespace: research,\n", + "\t\t\tdescription: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [\n", + "\t\t\n", + "\t\t\tid: 694561e9-df15-4c71-ae71-abd91df3ec23\n", + "\t\t\tname: Machine-1\n", + "\t\t\tslug: machine-1-694561e9\n", + "\t\t\tktype_id: testing-machine\n", + "\t\t\tsummary: None\n", + "\t\t\tavatar_exists: False\n", + "\t\t\tannotations: []\n", + "\t\t\tlinked_kitems: [{\n", + "\t\t\tid: 83672cdb-7584-4dd7-9b6e-d10574f1adf7\n", + "\t\t}, {\n", + "\t\t\tid: 84c089e6-feab-4ba6-a934-527594e504eb\n", + "\t\t}]\n", + "\t\t\texternal_links: []\n", + "\t\t\tcontacts: []\n", + "\t\t\tauthors: [{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}]\n", + "\t\t\tlinked_affiliations: []\n", + "\t\t\tattachments: []\n", + "\t\t\tuser_groups: []\n", + "\t\t\tcustom_properties: {'Producer': 'TestingLab GmBH', 'Room Number': 'A404', 'Description': 'Bending Test Machine'}\n", + "\t\t\tcreated_at: 2024-08-19T18:26:08.898435\n", + "\t\t\tupdated_at: 2024-08-19T18:26:08.898435\n", + "\t\t\n", + "\t], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:26:09.390800, \n", + "\n", + "\tupdated_at = 2024-08-19 18:26:09.390800, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = None, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n", + "\n", + "\n" + ] + } + ], "source": [ - "dsms.search(\n", - " ktypes=[dsms.ktypes.Organization], annotations=[\"www.researchBACiri.org/foo\"], limit=1\n", - " )" + "for result in dsms.search(\n", + " ktypes=[dsms.ktypes.Organization], annotations=[\"www.researchBACiri.org/foo\"], limit=1\n", + " ):\n", + " print(result.hit)\n", + " print(\"\\n\")" ] }, { @@ -204,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -216,11 +459,6 @@ "\n", "dsms.commit()" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] } ], "metadata": { @@ -239,7 +477,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/6_app_HDF5.ipynb b/docs/dsms_sdk/tutorials/6_app_HDF5.ipynb deleted file mode 100644 index 1ba2299..0000000 --- a/docs/dsms_sdk/tutorials/6_app_HDF5.ipynb +++ /dev/null @@ -1,152 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 6. App availability and HDF5\n", - "\n", - "In this tutorial we see how to search existing Kitems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 6.1: Setting up\n", - "\n", - "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", - "\n", - "\n", - "Now let us import the needed classes and functions for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from dsms import DSMS, KItem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now source the environmental variables from an `.env` file and start the DSMS-session." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms = DSMS(env=\"../../../.env\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 6.1. Investigating Available Apps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can investigate which apps are available through JupyterLab:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.apps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 6.2. Extraction into Data Frame for further analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are also able to upload dataframes or time series data and investigate them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = {\"a\": list(range(100)), \"b\": list(range(1,101))}\n", - "\n", - "\n", - "item = KItem(name=\"testdata123\", ktype_id=dsms.ktypes.DatasetCatalog, hdf5=data)\n", - "dsms.commit()\n", - "\n", - "print(\"Column-wise:\")\n", - "for column in item.hdf5:\n", - " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n", - "\n", - "df = item.hdf5.to_df()\n", - "print(\"\\nAs data frame:\")\n", - "print(df)\n", - "\n", - "new_df = df.drop(['a'], axis=1)\n", - "item.hdf5 = new_df\n", - "\n", - "dsms.commit()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "del dsms[item]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.commit()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/dsms_sdk/tutorials/6_apps.ipynb b/docs/dsms_sdk/tutorials/6_apps.ipynb new file mode 100644 index 0000000..b80dc22 --- /dev/null +++ b/docs/dsms_sdk/tutorials/6_apps.ipynb @@ -0,0 +1,1073 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6. DSMS Apps and Pipelines\n", + "\n", + "In this tutorial we see how to create apps and run them manually" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1: Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", + "\n", + "\n", + "Now let us import the needed classes and functions for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS, KItem, AppConfig\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\".env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1. Investigating Available Apps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate which apps are available:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[AppConfig(name=ckan-fetch, specification={'metadata': {'generateName': 'ckan-resource-request-'}}),\n", + " AppConfig(name=csv_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=csv_tensile_test_f2, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_notched_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_shear_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=ternary-plot, specification={'metadata': {'generateName': 'ckan-tenary-app-'}}),\n", + " AppConfig(name=testapp2, specification={'metadata': {'generateName': 'data2rdf-'}})]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.app_configs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2 Create a new app config and apply it to a KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2.1 Arbitrary python code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To be defined." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2.2 - Data2RDF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.1 Prepare app and its config" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following example, we would like to upload some csv with some arbitrary data and describe it through an RDF. This will give us the opportunity to harmonize the entities of the data file through ontological concepts and allow us to convert values of the data frame columns in to any compatible unit we would like to have.\n", + "\n", + "Fist of all, let us define the data:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"\"\"A,B,C\n", + "1.2,1.3,1.5\n", + "1.7,1.8,1.9\n", + "2.0,2.1,2.3\n", + "2.5,2.6,2.8\n", + "3.0,3.2,3.4\n", + "3.6,3.7,3.9\n", + "4.1,4.3,4.4\n", + "4.7,4.8,5.0\n", + "5.2,5.3,5.5\n", + "5.8,6.0,6.1\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also give the config a defined name:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "configname = \"testapp2\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a next step, we want to create a new app specification. The specification is following the definition of an [**Argo Workflow**](https://argo-workflows.readthedocs.io/en/latest/). The workflow shall trigger a pipeline from a docker image with the [**Data2RDF package**](https://data2rdf.readthedocs.io/en/latest/). \n", + "\n", + "The image has already been deployed on the k8s cluster of the DSMS and the workflow template with the name `dsms-data2rdf` has been implemented previously. Hence we only need to configure our pipeline for our data shown above, which we would like to upload and describe through an RDF.\n", + "\n", + "For more details about the data2rdf package, please refer to the documentation of Data2RDF mentioned above.\n", + "\n", + "The parameters of the app config are defining the inputs for our Data2RDF pipeline. This e.g. are: \n", + "\n", + "* the parser kind (`csv` here)\n", + "* the time series header length (`1` here)\n", + "* the metadata length (`0` here)\n", + "* the time series separator (`,` here)\n", + "* the log level (`DEBUG` here)\n", + "* the mapping \n", + " * `A` is the test time and has a unit in seconds\n", + " * `B` is the standard force in kilonewtons\n", + " * `C` is the absolut cross head travel in millimeters" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "parameters = [\n", + " {\"name\": \"parser\", \"value\": \"csv\"},\n", + " {\"name\": \"time_series_header_length\", \"value\": 1},\n", + " {\"name\": \"metadata_length\", \"value\": 0},\n", + " {\"name\": \"time_series_sep\", \"value\": \",\"},\n", + " {\"name\": \"log_level\", \"value\": \"DEBUG\"},\n", + " {\n", + " \"name\": \"mapping\",\n", + " \"value\": \"\"\"\n", + " [\n", + " {\n", + " \"key\": \"A\",\n", + " \"iri\": \"https://w3id.org/steel/ProcessOntology/TestTime\",\n", + " \"unit\": \"s\"\n", + " },\n", + " {\n", + " \"key\": \"B\",\n", + " \"iri\": \"https://w3id.org/steel/ProcessOntology/StandardForce\",\n", + " \"unit\": \"kN\"\n", + " },\n", + " {\n", + " \"key\": \"C\",\n", + " \"iri\": \"https://w3id.org/steel/ProcessOntology/AbsoluteCrossheadTravel\",\n", + " \"unit\": \"mm\"\n", + " }\n", + " ]\n", + " \"\"\",\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we add the parameters to our app specification. We assign a prefix `datardf-` which shall generate a new name with some random characters as suffix. The workflow template with the Docker image we want to run is called `dsms-data2rdf` and its `entrypoint` is `execute_pipeline`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Define app specification\n", + "specification = {\n", + " \"apiVersion\": \"argoproj.io/v1alpha1\",\n", + " \"kind\": \"Workflow\",\n", + " \"metadata\": {\"generateName\": \"data2rdf-\"},\n", + " \"spec\": {\n", + " \"entrypoint\": \"execute_pipeline\",\n", + " \"workflowTemplateRef\": {\"name\": \"dsms-data2rdf\"},\n", + " \"arguments\": {\"parameters\": parameters},\n", + " },\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we instanciate the new app config:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "appspec = AppConfig(\n", + " name=configname,\n", + " specification=specification, # this can also be a file path to a yaml file instead of a dict\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We commit the new app config:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we would like to apply the app config to a KItem. The set the `triggerUponUpload` must be set to `True` so that the app is triggered automatically when we upload an attachment.\n", + "\n", + "Additionally, we must tell the file extension for which the upload shall be triggered. Here it is `.csv`.\n", + "\n", + "We also want to generate a qr code as avatar for the KItem with `avatar={\"include_qr\": True}`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "item = KItem(\n", + " name=\"my tensile test experiment\",\n", + " ktype_id=dsms.ktypes.Dataset,\n", + " kitem_apps=[\n", + " {\n", + " \"executable\": appspec.name,\n", + " \"title\": \"data2rdf\",\n", + " \"additional_properties\": {\n", + " \"triggerUponUpload\": True,\n", + " \"triggerUponUploadFileExtensions\": [\".csv\"],\n", + " },\n", + " }\n", + " ],\n", + " avatar={\"include_qr\": True},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We commit the KItem:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we add our data with our attachment:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "item.attachments = [{\"name\": \"dummy_data.csv\", \"content\": data}]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And we commit again:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.2 Get results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can verify that the data extraction was successful:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = my tensile test experiment, \n", + "\n", + "\tid = 047c3e0e-0c4c-4815-b041-5251c2ef0882, \n", + "\n", + "\tktype_id = dataset, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = mytensiletestexperiment-047c3e0e, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: dummy_data.csv\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = True, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 19:27:11.826889, \n", + "\n", + "\tupdated_at = 2024-08-19 19:27:11.826889, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [\n", + "\t\t{\n", + "\t\t\tkitem_app_id: 21,\n", + "\t\t\texecutable: testapp2,\n", + "\t\t\ttitle: data2rdf,\n", + "\t\t\tdescription: None,\n", + "\t\t\ttags: None,\n", + "\t\t\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv']}\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = None, \n", + "\n", + "\tdataframe = [\n", + "\t\t{\n", + "\t\t\tcolumn_id: 0,\n", + "\t\t\tname: TestTime\n", + "\t\t}, \n", + "\t\t{\n", + "\t\t\tcolumn_id: 1,\n", + "\t\t\tname: StandardForce\n", + "\t\t}, \n", + "\t\t{\n", + "\t\t\tcolumn_id: 2,\n", + "\t\t\tname: AbsoluteCrossheadTravel\n", + "\t\t}\n", + "\t], \n", + "\n", + "\trdf_exists = True\n", + ")\n" + ] + } + ], + "source": [ + "print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And also that the RDF generation was successful:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "@prefix csvw: .\n", + "@prefix dcat: .\n", + "@prefix dcterms: .\n", + "@prefix ns1: .\n", + "@prefix ns2: .\n", + "@prefix rdfs: .\n", + "@prefix xsd: .\n", + "\n", + " a dcat:Dataset ;\n", + " dcterms:hasPart ;\n", + " dcat:distribution [ a dcat:Distribution ;\n", + " dcat:accessURL \"https://bue.materials-data.space/api/knowledge/data_api/047c3e0e-0c4c-4815-b041-5251c2ef0882\"^^xsd:anyURI ;\n", + " dcat:mediaType \"http://www.iana.org/assignments/media-types/text/csv\"^^xsd:anyURI ] .\n", + "\n", + " a ;\n", + " ns1:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI .\n", + "\n", + " a ;\n", + " ns1:hasUnit \"http://qudt.org/vocab/unit/KiloN\"^^xsd:anyURI .\n", + "\n", + " a ;\n", + " ns1:hasUnit \"http://qudt.org/vocab/unit/SEC\"^^xsd:anyURI .\n", + "\n", + " a csvw:TableGroup ;\n", + " csvw:table [ a csvw:Table ;\n", + " rdfs:label \"Time series data\" ;\n", + " csvw:tableSchema [ a csvw:Schema ;\n", + " csvw:column [ a csvw:Column ;\n", + " ns1:quantity ;\n", + " csvw:titles \"C\"^^xsd:string ;\n", + " ns2:page [ a ns2:Document ;\n", + " dcterms:format \"https://www.iana.org/assignments/media-types/application/json\"^^xsd:anyURI ;\n", + " dcterms:identifier \"https://bue.materials-data.space/api/knowledge/data_api/column-2\"^^xsd:anyURI ;\n", + " dcterms:type \"http://purl.org/dc/terms/Dataset\"^^xsd:anyURI ] ],\n", + " [ a csvw:Column ;\n", + " ns1:quantity ;\n", + " csvw:titles \"A\"^^xsd:string ;\n", + " ns2:page [ a ns2:Document ;\n", + " dcterms:format \"https://www.iana.org/assignments/media-types/application/json\"^^xsd:anyURI ;\n", + " dcterms:identifier \"https://bue.materials-data.space/api/knowledge/data_api/column-0\"^^xsd:anyURI ;\n", + " dcterms:type \"http://purl.org/dc/terms/Dataset\"^^xsd:anyURI ] ],\n", + " [ a csvw:Column ;\n", + " ns1:quantity ;\n", + " csvw:titles \"B\"^^xsd:string ;\n", + " ns2:page [ a ns2:Document ;\n", + " dcterms:format \"https://www.iana.org/assignments/media-types/application/json\"^^xsd:anyURI ;\n", + " dcterms:identifier \"https://bue.materials-data.space/api/knowledge/data_api/column-1\"^^xsd:anyURI ;\n", + " dcterms:type \"http://purl.org/dc/terms/Dataset\"^^xsd:anyURI ] ] ] ] .\n", + "\n", + "\n" + ] + } + ], + "source": [ + "print(item.subgraph.serialize())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we are able to convert our data into any compatiable unit we want. For the `StandardForce`, it was previously `kN`, but we want to have it in `N` now:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1300.0,\n", + " 1800.0,\n", + " 2100.0,\n", + " 2600.0,\n", + " 3200.0,\n", + " 3700.0,\n", + " 4300.0,\n", + " 4800.0,\n", + " 5300.0,\n", + " 6000.0]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item.dataframe.StandardForce.convert_to(\"N\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.3 Manipulate dataframe" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to retrieve the dataframe as pd.DataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    TestTimeStandardForceAbsoluteCrossheadTravel
    01.21.31.5
    11.71.81.9
    22.02.12.3
    32.52.62.8
    43.03.23.4
    53.63.73.9
    64.14.34.4
    74.74.85.0
    85.25.35.5
    95.86.06.1
    \n", + "
    " + ], + "text/plain": [ + " TestTime StandardForce AbsoluteCrossheadTravel\n", + "0 1.2 1.3 1.5\n", + "1 1.7 1.8 1.9\n", + "2 2.0 2.1 2.3\n", + "3 2.5 2.6 2.8\n", + "4 3.0 3.2 3.4\n", + "5 3.6 3.7 3.9\n", + "6 4.1 4.3 4.4\n", + "7 4.7 4.8 5.0\n", + "8 5.2 5.3 5.5\n", + "9 5.8 6.0 6.1" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item.dataframe.to_df()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to overwrite the dataframe with new data:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "item.dataframe = {\n", + " \"TestTime\": list(range(100)),\n", + " \"StandardForce\": list(range(1,101)),\n", + " \"AbsoluteCrossheadTravel\": list(range(2,102))\n", + "}\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to retrieve the data colum-wise:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "column: TestTime ,\n", + " data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]\n", + "column: StandardForce ,\n", + " data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]\n", + "column: AbsoluteCrossheadTravel ,\n", + " data: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101]\n" + ] + } + ], + "source": [ + "for column in item.dataframe:\n", + " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and also to modify the dataframe directly as we need:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "new_df = item.dataframe.to_df().drop(['TestTime'], axis=1)\n", + "item.dataframe = new_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.4 Run app on demand" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to run the app on demand, not being triggered automatically during the upload of an attachment every time. For this purpose, we just need to refer to the name of the app we assigned during the KItem creation ( here it is simply `data2rdf`).\n", + "\n", + "Additionally, we need to tell the `attachment_name` and hand over the access token and host url to the app by explicitly setting `set_token` and `set_host_url` to `True`.\n", + "\n", + "The app is running synchronously, hence the `job` is created when the pipeline run finished." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "job = item.kitem_apps.by_title[\"data2rdf\"].run(\n", + " attachment_name=item.attachments[0].name,\n", + " set_token=True,\n", + " set_host_url=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to retrieve the job status:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "JobStatus(phase='Succeeded', estimated_duration=None, finished_at='08/19/2024, 19:28:06', started_at='08/19/2024, 19:27:43', message=None, progress='1/1')" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job.status" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and the job logs:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\"[2024-08-19 19:27:49,060 - dsms_data2rdf.main - INFO]: Fetch KItem: \\n KItem(\\n\\n\\tname = my tensile test experiment, \\n\\n\\tid = 047c3e0e-0c4c-4815-b041-5251c2ef0882, \\n\\n\\tktype_id = dataset, \\n\\n\\tin_backend = True, \\n\\n\\tslug = mytensiletestexperiment-047c3e0e, \\n\\n\\tannotations = [], \\n\\n\\tattachments = [\\n\\t\\t{\\n\\t\\t\\tname: dummy_data.csv\\n\\t\\t}\\n\\t], \\n\\n\\tlinked_kitems = [], \\n\\n\\taffiliations = [], \\n\\n\\tauthors = [\\n\\t\\t{\\n\\t\\t\\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\\n\\t\\t}\\n\\t], \\n\\n\\tavatar_exists = True, \\n\\n\\tcontacts = [], \\n\\n\\tcreated_at = 2024-08-19 19:27:11.826889, \\n\\n\\tupdated_at = 2024-08-19 19:27:11.826889, \\n\\n\\texternal_links = [], \\n\\n\\tkitem_apps = [\\n\\t\\t{\\n\\t\\t\\tkitem_app_id: 21,\\n\\t\\t\\texecutable: testapp2,\\n\\t\\t\\ttitle: data2rdf,\\n\\t\\t\\tdescription: None,\\n\\t\\t\\ttags: None,\\n\\t\\t\\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv']}\\n\\t\\t}\\n\\t], \\n\\n\\tsummary = None, \\n\\n\\tuser_groups = [], \\n\\n\\tcustom_properties = None, \\n\\n\\tdataframe = [\\n\\t\\t{\\n\\t\\t\\tcolumn_id: 0,\\n\\t\\t\\tname: TestTime\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 1,\\n\\t\\t\\tname: StandardForce\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 2,\\n\\t\\t\\tname: AbsoluteCrossheadTravel\\n\\t\\t}\\n\\t], \\n\\n\\trdf_exists = True\\n)\\n[2024-08-19 19:27:49,073 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser arguments: {'metadata_sep': ',', 'metadata_length': '0', 'time_series_sep': ',', 'time_series_header_length': '1', 'drop_na': 'false', 'fillna': ''}\\n[2024-08-19 19:27:49,073 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser: Parser.csv\\n[2024-08-19 19:27:49,073 - dsms_data2rdf.main - INFO]: Run pipeline with the following config: {'base_iri': 'https://bue.materials-data.space/047c3e0e-0c4c-4815-b041-5251c2ef0882', 'data_download_uri': 'https://bue.materials-data.space/api/knowledge/data_api/047c3e0e-0c4c-4815-b041-5251c2ef0882', 'graph_identifier': 'https://bue.materials-data.space/047c3e0e-0c4c-4815-b041-5251c2ef0882', 'separator': '/', 'encoding': 'utf-8'}\\n[2024-08-19 19:27:54,079 - dsms_data2rdf.main - INFO]: Pipeline did detect any metadata. Will not make annotations for KItem\\n[2024-08-19 19:27:54,455 - dsms_data2rdf.main - INFO]: Checking that dataframe is up to date.\\n[2024-08-19 19:27:54,455 - dsms_data2rdf.main - INFO]: Dataframe upload was successful after 0 retries.\\n[2024-08-19 19:27:54,456 - dsms_data2rdf.main - INFO]: Done!\\n\"\n" + ] + } + ], + "source": [ + "print(job.logs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In case we would like to run the job in the background, we simply add a `wait=False`:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "job = item.kitem_apps.by_title[\"data2rdf\"].run(\n", + " attachment_name=item.attachments[0].name,\n", + " set_token=True,\n", + " set_host_url=True,\n", + " wait=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to monitor the job status and logs asynchronously:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/19/2024, 19:28:06' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Succeeded' estimated_duration=None finished_at='08/19/2024, 19:28:26' started_at='08/19/2024, 19:28:06' message=None progress='1/1'\n", + "\n", + " Current logs:\n", + "\"[2024-08-19 19:28:13,519 - dsms_data2rdf.main - INFO]: Fetch KItem: \\n KItem(\\n\\n\\tname = my tensile test experiment, \\n\\n\\tid = 047c3e0e-0c4c-4815-b041-5251c2ef0882, \\n\\n\\tktype_id = dataset, \\n\\n\\tin_backend = True, \\n\\n\\tslug = mytensiletestexperiment-047c3e0e, \\n\\n\\tannotations = [], \\n\\n\\tattachments = [\\n\\t\\t{\\n\\t\\t\\tname: dummy_data.csv\\n\\t\\t}\\n\\t], \\n\\n\\tlinked_kitems = [], \\n\\n\\taffiliations = [], \\n\\n\\tauthors = [\\n\\t\\t{\\n\\t\\t\\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\\n\\t\\t}\\n\\t], \\n\\n\\tavatar_exists = True, \\n\\n\\tcontacts = [], \\n\\n\\tcreated_at = 2024-08-19 19:27:11.826889, \\n\\n\\tupdated_at = 2024-08-19 19:27:11.826889, \\n\\n\\texternal_links = [], \\n\\n\\tkitem_apps = [\\n\\t\\t{\\n\\t\\t\\tkitem_app_id: 21,\\n\\t\\t\\texecutable: testapp2,\\n\\t\\t\\ttitle: data2rdf,\\n\\t\\t\\tdescription: None,\\n\\t\\t\\ttags: None,\\n\\t\\t\\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv']}\\n\\t\\t}\\n\\t], \\n\\n\\tsummary = None, \\n\\n\\tuser_groups = [], \\n\\n\\tcustom_properties = None, \\n\\n\\tdataframe = [\\n\\t\\t{\\n\\t\\t\\tcolumn_id: 0,\\n\\t\\t\\tname: TestTime\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 1,\\n\\t\\t\\tname: StandardForce\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 2,\\n\\t\\t\\tname: AbsoluteCrossheadTravel\\n\\t\\t}\\n\\t], \\n\\n\\trdf_exists = True\\n)\\n[2024-08-19 19:28:13,532 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser arguments: {'metadata_sep': ',', 'metadata_length': '0', 'time_series_sep': ',', 'time_series_header_length': '1', 'drop_na': 'false', 'fillna': ''}\\n[2024-08-19 19:28:13,532 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser: Parser.csv\\n[2024-08-19 19:28:13,532 - dsms_data2rdf.main - INFO]: Run pipeline with the following config: {'base_iri': 'https://bue.materials-data.space/047c3e0e-0c4c-4815-b041-5251c2ef0882', 'data_download_uri': 'https://bue.materials-data.space/api/knowledge/data_api/047c3e0e-0c4c-4815-b041-5251c2ef0882', 'graph_identifier': 'https://bue.materials-data.space/047c3e0e-0c4c-4815-b041-5251c2ef0882', 'separator': '/', 'encoding': 'utf-8'}\\n[2024-08-19 19:28:18,546 - dsms_data2rdf.main - INFO]: Pipeline did detect any metadata. Will not make annotations for KItem\\n[2024-08-19 19:28:18,929 - dsms_data2rdf.main - INFO]: Checking that dataframe is up to date.\\n[2024-08-19 19:28:18,929 - dsms_data2rdf.main - INFO]: Dataframe upload was successful after 0 retries.\\n[2024-08-19 19:28:18,929 - dsms_data2rdf.main - INFO]: Done!\\n\"\n" + ] + } + ], + "source": [ + "while True:\n", + " time.sleep(1)\n", + " print(\"\\n Current status:\")\n", + " print(job.status)\n", + " print(\"\\n Current logs:\")\n", + " print(job.logs)\n", + " if job.status.phase != \"Running\":\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**IMPORTANT**: When job has run asychronously (in the background), we need to manually refresh the KItem afterwards:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "item.refresh()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Clean up the DSMS from the tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]\n", + "del dsms[appspec]\n", + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/dsms_sdk/tutorials/testfile.txt b/docs/dsms_sdk/tutorials/testfile.txt new file mode 100755 index 0000000..7976166 --- /dev/null +++ b/docs/dsms_sdk/tutorials/testfile.txt @@ -0,0 +1 @@ +This is a calibration protocol! diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 5565bd8..7f30f0f 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -129,7 +129,7 @@ class KItem(BaseModel): slug: Optional[str] = Field( None, description="Slug of the KContext.dsms", min_length=4 ) - annotations: List[Annotation] = Field( + annotations: List[Union[str, Annotation]] = Field( [], description="Annotations of the KItem" ) attachments: List[Union[Attachment, str]] = Field( diff --git a/examples/unit_conversion.ipynb b/examples/unit_conversion.ipynb deleted file mode 100644 index 0d7844b..0000000 --- a/examples/unit_conversion.ipynb +++ /dev/null @@ -1,1265 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# DSMS SDK unit conversion example " - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "from dsms import DSMS\n", - "from dsms.knowledge.semantics.units import get_conversion_factor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Start session" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "dsms = DSMS(host_url=\"https://stahldigital.materials-data.space\", env=\"../.env\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Meters to millimeters" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1000.0" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_conversion_factor(\"m\", \"mm\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Kilometers to Inches: we can also round the factor in decimal places" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "39370.1" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_conversion_factor(\"km\", \"in\", decimals=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### GigaPascal to MegaPascal" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1000.0" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_conversion_factor(\"GPa\", \"MPa\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### We can also use the qudt IRIs" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "39.37" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_conversion_factor(\n", - " \"http://qudt.org/vocab/unit/M\", \"http://qudt.org/vocab/unit/IN\", decimals=2\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### KiloPascal to Centimeter: this will raise an error because the units are not compatible" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Unit http://qudt.org/vocab/unit/KiloPA can numerically not be converted into http://qudt.org/vocab/unit/CentiM\n" - ] - } - ], - "source": [ - "try:\n", - " get_conversion_factor(\"kPa\", \"cm\")\n", - "except ValueError as error:\n", - " print(error.args[0])\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Something more complicated: Micromoles per litre day into Reciprocal Second Square Meter" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1.15740740740741e-08" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_conversion_factor(\"µm/(L·day)\", \"s⁻¹·m⁻²\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also directly convert the columns of a dataframe into a unit of interest." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original unit: {'symbol': 'N', 'iri': 'http://emmo.info/emmo/middle/siunits#EMMO_a979c531_f9fa_4a6e_93c1_a2960241ca64'}\n", - "\n", - "\n", - "Now the converted data column in kN\n" - ] - }, - { - "data": { - "text/plain": [ - "[0.0819691,\n", - " 0.0820348,\n", - " 0.0820854,\n", - " 0.0820619,\n", - " 0.0820386,\n", - " 0.0820924,\n", - " 0.0821014,\n", - " 0.08210890000000001,\n", - " 0.0821103,\n", - " 0.0822442,\n", - " 0.0827859,\n", - " 0.08363190000000001,\n", - " 0.0846336,\n", - " 0.0856447,\n", - " 0.0866605,\n", - " 0.0876807,\n", - " 0.0886995,\n", - " 0.0897311,\n", - " 0.0907341,\n", - " 0.0917502,\n", - " 0.09278,\n", - " 0.09379269999999999,\n", - " 0.0948254,\n", - " 0.09582760000000001,\n", - " 0.096832,\n", - " 0.09786990000000001,\n", - " 0.0988997,\n", - " 0.0999365,\n", - " 0.100964,\n", - " 0.101998,\n", - " 0.10302800000000001,\n", - " 0.104029,\n", - " 0.105075,\n", - " 0.106114,\n", - " 0.10714,\n", - " 0.10819100000000001,\n", - " 0.109229,\n", - " 0.110281,\n", - " 0.111333,\n", - " 0.112383,\n", - " 0.113418,\n", - " 0.11445699999999999,\n", - " 0.11551399999999999,\n", - " 0.116562,\n", - " 0.11763,\n", - " 0.1187,\n", - " 0.119718,\n", - " 0.12075100000000001,\n", - " 0.12181600000000001,\n", - " 0.12288500000000001,\n", - " 0.123891,\n", - " 0.124923,\n", - " 0.125973,\n", - " 0.127044,\n", - " 0.12804400000000002,\n", - " 0.129065,\n", - " 0.130079,\n", - " 0.131119,\n", - " 0.13218000000000002,\n", - " 0.13324100000000003,\n", - " 0.134311,\n", - " 0.135313,\n", - " 0.13631800000000002,\n", - " 0.137338,\n", - " 0.13836099999999998,\n", - " 0.139367,\n", - " 0.140405,\n", - " 0.141445,\n", - " 0.14248,\n", - " 0.143527,\n", - " 0.144568,\n", - " 0.14561500000000002,\n", - " 0.14665799999999998,\n", - " 0.14772100000000002,\n", - " 0.148785,\n", - " 0.149847,\n", - " 0.150923,\n", - " 0.151993,\n", - " 0.153091,\n", - " 0.15418,\n", - " 0.15527000000000002,\n", - " 0.156367,\n", - " 0.15746700000000002,\n", - " 0.158492,\n", - " 0.159524,\n", - " 0.160537,\n", - " 0.161571,\n", - " 0.16262000000000001,\n", - " 0.16367400000000001,\n", - " 0.164722,\n", - " 0.165773,\n", - " 0.16685400000000003,\n", - " 0.167942,\n", - " 0.16902799999999998,\n", - " 0.170118,\n", - " 0.171206,\n", - " 0.172313,\n", - " 0.17341800000000002,\n", - " 0.174435,\n", - " 0.175474,\n", - " 0.17650100000000002,\n", - " 0.17753200000000002,\n", - " 0.178574,\n", - " 0.179602,\n", - " 0.180634,\n", - " 0.18166800000000002,\n", - " 0.18272300000000002,\n", - " 0.18377500000000002,\n", - " 0.18484,\n", - " 0.185898,\n", - " 0.18697,\n", - " 0.188062,\n", - " 0.189148,\n", - " 0.19023500000000002,\n", - " 0.19131700000000001,\n", - " 0.19243000000000002,\n", - " 0.193545,\n", - " 0.194664,\n", - " 0.195666,\n", - " 0.196791,\n", - " 0.197792,\n", - " 0.19891399999999998,\n", - " 0.199915,\n", - " 0.200917,\n", - " 0.202035,\n", - " 0.203046,\n", - " 0.204075,\n", - " 0.2051,\n", - " 0.206147,\n", - " 0.207202,\n", - " 0.208254,\n", - " 0.20929499999999998,\n", - " 0.21035800000000002,\n", - " 0.211423,\n", - " 0.212478,\n", - " 0.213535,\n", - " 0.214605,\n", - " 0.215678,\n", - " 0.216763,\n", - " 0.217838,\n", - " 0.21890600000000002,\n", - " 0.219975,\n", - " 0.221052,\n", - " 0.22215000000000001,\n", - " 0.223251,\n", - " 0.224351,\n", - " 0.225456,\n", - " 0.226578,\n", - " 0.227708,\n", - " 0.22871100000000003,\n", - " 0.229712,\n", - " 0.230716,\n", - " 0.231718,\n", - " 0.23286,\n", - " 0.233988,\n", - " 0.23512200000000003,\n", - " 0.236136,\n", - " 0.23715,\n", - " 0.238162,\n", - " 0.23919300000000002,\n", - " 0.240231,\n", - " 0.241262,\n", - " 0.242287,\n", - " 0.243316,\n", - " 0.244346,\n", - " 0.245377,\n", - " 0.246418,\n", - " 0.247471,\n", - " 0.248542,\n", - " 0.249613,\n", - " 0.250686,\n", - " 0.251741,\n", - " 0.252801,\n", - " 0.253871,\n", - " 0.254951,\n", - " 0.25602800000000003,\n", - " 0.25711700000000004,\n", - " 0.25819600000000004,\n", - " 0.259282,\n", - " 0.26036200000000004,\n", - " 0.261452,\n", - " 0.262552,\n", - " 0.263659,\n", - " 0.264779,\n", - " 0.265905,\n", - " 0.267023,\n", - " 0.26813200000000004,\n", - " 0.26924400000000004,\n", - " 0.270358,\n", - " 0.27148300000000003,\n", - " 0.27261900000000006,\n", - " 0.27376100000000003,\n", - " 0.27490499999999995,\n", - " 0.27606200000000003,\n", - " 0.277214,\n", - " 0.27835899999999997,\n", - " 0.27950400000000003,\n", - " 0.280652,\n", - " 0.2818,\n", - " 0.282957,\n", - " 0.283963,\n", - " 0.284974,\n", - " 0.28598,\n", - " 0.287146,\n", - " 0.288148,\n", - " 0.289151,\n", - " 0.290171,\n", - " 0.29119,\n", - " 0.29221199999999997,\n", - " 0.293236,\n", - " 0.294262,\n", - " 0.295286,\n", - " 0.296313,\n", - " 0.297333,\n", - " 0.298352,\n", - " 0.29936900000000005,\n", - " 0.300394,\n", - " 0.30143200000000003,\n", - " 0.30248,\n", - " 0.303524,\n", - " 0.304564,\n", - " 0.305611,\n", - " 0.30666000000000004,\n", - " 0.30770600000000004,\n", - " 0.308751,\n", - " 0.309799,\n", - " 0.310849,\n", - " 0.311906,\n", - " 0.31297,\n", - " 0.314039,\n", - " 0.315106,\n", - " 0.316166,\n", - " 0.317229,\n", - " 0.318288,\n", - " 0.319349,\n", - " 0.320409,\n", - " 0.321476,\n", - " 0.322549,\n", - " 0.32362,\n", - " 0.32469,\n", - " 0.325758,\n", - " 0.326818,\n", - " 0.32787900000000003,\n", - " 0.32895100000000005,\n", - " 0.330022,\n", - " 0.331087,\n", - " 0.33216,\n", - " 0.333237,\n", - " 0.334306,\n", - " 0.335384,\n", - " 0.336463,\n", - " 0.337534,\n", - " 0.338606,\n", - " 0.339687,\n", - " 0.340767,\n", - " 0.34185000000000004,\n", - " 0.342943,\n", - " 0.344037,\n", - " 0.345123,\n", - " 0.346212,\n", - " 0.34730500000000003,\n", - " 0.348402,\n", - " 0.349494,\n", - " 0.350592,\n", - " 0.35170100000000004,\n", - " 0.352801,\n", - " 0.35390499999999997,\n", - " 0.355011,\n", - " 0.356119,\n", - " 0.357233,\n", - " 0.35834699999999997,\n", - " 0.359462,\n", - " 0.360576,\n", - " 0.361702,\n", - " 0.362843,\n", - " 0.363991,\n", - " 0.365137,\n", - " 0.366287,\n", - " 0.367437,\n", - " 0.368587,\n", - " 0.369739,\n", - " 0.370897,\n", - " 0.37205200000000005,\n", - " 0.373214,\n", - " 0.374373,\n", - " 0.37553800000000004,\n", - " 0.376709,\n", - " 0.377877,\n", - " 0.379041,\n", - " 0.380214,\n", - " 0.38138299999999997,\n", - " 0.382556,\n", - " 0.38373399999999996,\n", - " 0.384919,\n", - " 0.386101,\n", - " 0.387281,\n", - " 0.388459,\n", - " 0.38963400000000004,\n", - " 0.390823,\n", - " 0.391824,\n", - " 0.392824,\n", - " 0.39382799999999996,\n", - " 0.39482799999999996,\n", - " 0.396023,\n", - " 0.397221,\n", - " 0.39842000000000005,\n", - " 0.39942,\n", - " 0.40061399999999997,\n", - " 0.401807,\n", - " 0.403003,\n", - " 0.404198,\n", - " 0.405384,\n", - " 0.40657400000000005,\n", - " 0.407575,\n", - " 0.408582,\n", - " 0.409597,\n", - " 0.41062200000000004,\n", - " 0.411643,\n", - " 0.412657,\n", - " 0.413677,\n", - " 0.41469900000000004,\n", - " 0.41571600000000003,\n", - " 0.41672800000000004,\n", - " 0.417742,\n", - " 0.41875799999999996,\n", - " 0.419772,\n", - " 0.420786,\n", - " 0.421801,\n", - " 0.422819,\n", - " 0.42384,\n", - " 0.424863,\n", - " 0.425894,\n", - " 0.426929,\n", - " 0.427968,\n", - " 0.429013,\n", - " 0.430059,\n", - " 0.431101,\n", - " 0.432139,\n", - " 0.433182,\n", - " 0.434225,\n", - " 0.435274,\n", - " 0.43632400000000005,\n", - " 0.437376,\n", - " 0.438433,\n", - " 0.4395,\n", - " 0.440574,\n", - " 0.441651,\n", - " 0.442724,\n", - " 0.44379,\n", - " 0.444853,\n", - " 0.445923,\n", - " 0.447003,\n", - " 0.44808800000000004,\n", - " 0.449168,\n", - " 0.450248,\n", - " 0.451325,\n", - " 0.452399,\n", - " 0.453483,\n", - " 0.45457600000000004,\n", - " 0.455667,\n", - " 0.45676,\n", - " 0.457857,\n", - " 0.458957,\n", - " 0.46005900000000005,\n", - " 0.461164,\n", - " 0.46227,\n", - " 0.463382,\n", - " 0.46449599999999996,\n", - " 0.465614,\n", - " 0.466729,\n", - " 0.467835,\n", - " 0.468944,\n", - " 0.470055,\n", - " 0.47116800000000003,\n", - " 0.47227600000000003,\n", - " 0.473382,\n", - " 0.474491,\n", - " 0.475601,\n", - " 0.476711,\n", - " 0.47782600000000003,\n", - " 0.47894600000000004,\n", - " 0.48007299999999997,\n", - " 0.481204,\n", - " 0.482331,\n", - " 0.48345,\n", - " 0.484559,\n", - " 0.485674,\n", - " 0.486798,\n", - " 0.487935,\n", - " 0.48907900000000004,\n", - " 0.49021800000000004,\n", - " 0.491359,\n", - " 0.492499,\n", - " 0.49363799999999997,\n", - " 0.49478,\n", - " 0.49592200000000003,\n", - " 0.497066,\n", - " 0.49821,\n", - " 0.499349,\n", - " 0.500486,\n", - " 0.501626,\n", - " 0.502774,\n", - " 0.503929,\n", - " 0.5050830000000001,\n", - " 0.506237,\n", - " 0.507391,\n", - " 0.508543,\n", - " 0.509699,\n", - " 0.5108550000000001,\n", - " 0.512009,\n", - " 0.51317,\n", - " 0.514334,\n", - " 0.5155,\n", - " 0.516663,\n", - " 0.5178360000000001,\n", - " 0.519009,\n", - " 0.520181,\n", - " 0.521361,\n", - " 0.522543,\n", - " 0.523721,\n", - " 0.5248980000000001,\n", - " 0.526073,\n", - " 0.527247,\n", - " 0.528424,\n", - " 0.52961,\n", - " 0.5307970000000001,\n", - " 0.531981,\n", - " 0.533164,\n", - " 0.53435,\n", - " 0.535537,\n", - " 0.536729,\n", - " 0.537928,\n", - " 0.539125,\n", - " 0.540325,\n", - " 0.5415209999999999,\n", - " 0.5427179999999999,\n", - " 0.543919,\n", - " 0.545122,\n", - " 0.546326,\n", - " 0.547535,\n", - " 0.548745,\n", - " 0.549952,\n", - " 0.55116,\n", - " 0.552377,\n", - " 0.553601,\n", - " 0.55483,\n", - " 0.55606,\n", - " 0.557289,\n", - " 0.558519,\n", - " 0.5597530000000001,\n", - " 0.5609890000000001,\n", - " 0.562219,\n", - " 0.5634560000000001,\n", - " 0.564699,\n", - " 0.565936,\n", - " 0.567177,\n", - " 0.568424,\n", - " 0.569667,\n", - " 0.570911,\n", - " 0.572155,\n", - " 0.573396,\n", - " 0.574641,\n", - " 0.575886,\n", - " 0.577126,\n", - " 0.57837,\n", - " 0.579615,\n", - " 0.580861,\n", - " 0.58211,\n", - " 0.5833579999999999,\n", - " 0.584605,\n", - " 0.585852,\n", - " 0.587091,\n", - " 0.5883339999999999,\n", - " 0.5893339999999999,\n", - " 0.590336,\n", - " 0.5913400000000001,\n", - " 0.592342,\n", - " 0.5933440000000001,\n", - " 0.5943440000000001,\n", - " 0.595346,\n", - " 0.596352,\n", - " 0.597364,\n", - " 0.598379,\n", - " 0.5993890000000001,\n", - " 0.6003970000000001,\n", - " 0.601401,\n", - " 0.602405,\n", - " 0.603414,\n", - " 0.60442,\n", - " 0.60542,\n", - " 0.606422,\n", - " 0.607428,\n", - " 0.6084360000000001,\n", - " 0.609448,\n", - " 0.610459,\n", - " 0.6114769999999999,\n", - " 0.612495,\n", - " 0.61351,\n", - " 0.6145280000000001,\n", - " 0.6155510000000001,\n", - " 0.616577,\n", - " 0.6176,\n", - " 0.618623,\n", - " 0.619649,\n", - " 0.620672,\n", - " 0.621698,\n", - " 0.6227229999999999,\n", - " 0.6237480000000001,\n", - " 0.6247780000000001,\n", - " 0.625811,\n", - " 0.626854,\n", - " 0.627893,\n", - " 0.628932,\n", - " 0.6299750000000001,\n", - " 0.631016,\n", - " 0.6320549999999999,\n", - " 0.63309,\n", - " 0.634129,\n", - " 0.63517,\n", - " 0.636213,\n", - " 0.6372580000000001,\n", - " 0.6383030000000001,\n", - " 0.6393500000000001,\n", - " 0.640389,\n", - " 0.641428,\n", - " 0.642473,\n", - " 0.64352,\n", - " 0.644569,\n", - " 0.64562,\n", - " 0.646672,\n", - " 0.647725,\n", - " 0.648768,\n", - " 0.649809,\n", - " 0.650852,\n", - " 0.651901,\n", - " 0.6529539999999999,\n", - " 0.654006,\n", - " 0.655057,\n", - " 0.656105,\n", - " 0.65716,\n", - " 0.6582210000000001,\n", - " 0.6592790000000001,\n", - " 0.66034,\n", - " 0.661398,\n", - " 0.6624589999999999,\n", - " 0.663519,\n", - " 0.6645800000000001,\n", - " 0.6656409999999999,\n", - " 0.6666989999999999,\n", - " 0.667752,\n", - " 0.668805,\n", - " 0.669861,\n", - " 0.670924,\n", - " 0.671986,\n", - " 0.6730430000000001,\n", - " 0.674103,\n", - " 0.6751699999999999,\n", - " 0.676234,\n", - " 0.677293,\n", - " 0.678348,\n", - " 0.679404,\n", - " 0.680471,\n", - " 0.6815410000000001,\n", - " 0.682611,\n", - " 0.683685,\n", - " 0.684762,\n", - " 0.685838,\n", - " 0.686914,\n", - " 0.687986,\n", - " 0.6890620000000001,\n", - " 0.690141,\n", - " 0.691221,\n", - " 0.692303,\n", - " 0.693389,\n", - " 0.694474,\n", - " 0.695557,\n", - " 0.696637,\n", - " 0.6977190000000001,\n", - " 0.698801,\n", - " 0.699883,\n", - " 0.7009690000000001,\n", - " 0.702055,\n", - " 0.703142,\n", - " 0.704236,\n", - " 0.70533,\n", - " 0.7064260000000001,\n", - " 0.707519,\n", - " 0.708613,\n", - " 0.7097129999999999,\n", - " 0.710818,\n", - " 0.71192,\n", - " 0.7130230000000001,\n", - " 0.714125,\n", - " 0.715228,\n", - " 0.71633,\n", - " 0.7174320000000001,\n", - " 0.718527,\n", - " 0.7196250000000001,\n", - " 0.720726,\n", - " 0.721836,\n", - " 0.722943,\n", - " 0.724047,\n", - " 0.725148,\n", - " 0.726256,\n", - " 0.727373,\n", - " 0.728492,\n", - " 0.729611,\n", - " 0.73073,\n", - " 0.731851,\n", - " 0.732974,\n", - " 0.734094,\n", - " 0.735207,\n", - " 0.7363200000000001,\n", - " 0.7374299999999999,\n", - " 0.7385410000000001,\n", - " 0.7396520000000001,\n", - " 0.7407670000000001,\n", - " 0.741887,\n", - " 0.7430059999999999,\n", - " 0.744121,\n", - " 0.745236,\n", - " 0.7463529999999999,\n", - " 0.747469,\n", - " 0.7485839999999999,\n", - " 0.749703,\n", - " 0.750832,\n", - " 0.7519710000000001,\n", - " 0.753105,\n", - " 0.754236,\n", - " 0.7553650000000001,\n", - " 0.756496,\n", - " 0.7576350000000001,\n", - " 0.758773,\n", - " 0.759914,\n", - " 0.761051,\n", - " 0.762189,\n", - " 0.7633260000000001,\n", - " 0.7644610000000001,\n", - " 0.76559,\n", - " 0.766721,\n", - " 0.7678550000000001,\n", - " 0.7689940000000001,\n", - " 0.770135,\n", - " 0.771279,\n", - " 0.7724260000000001,\n", - " 0.773572,\n", - " 0.774721,\n", - " 0.775873,\n", - " 0.777021,\n", - " 0.77817,\n", - " 0.779324,\n", - " 0.7804840000000001,\n", - " 0.781644,\n", - " 0.7828010000000001,\n", - " 0.7839550000000001,\n", - " 0.785107,\n", - " 0.786258,\n", - " 0.787412,\n", - " 0.7885700000000001,\n", - " 0.789732,\n", - " 0.7908959999999999,\n", - " 0.79206,\n", - " 0.7932279999999999,\n", - " 0.794396,\n", - " 0.795566,\n", - " 0.79674,\n", - " 0.797913,\n", - " 0.799085,\n", - " 0.800257,\n", - " 0.8014330000000001,\n", - " 0.8026150000000001,\n", - " 0.803794,\n", - " 0.804976,\n", - " 0.8061630000000001,\n", - " 0.807355,\n", - " 0.8085399999999999,\n", - " 0.8097179999999999,\n", - " 0.810898,\n", - " 0.8120850000000001,\n", - " 0.813277,\n", - " 0.814466,\n", - " 0.815652,\n", - " 0.8168390000000001,\n", - " 0.818035,\n", - " 0.819232,\n", - " 0.820429,\n", - " 0.8216319999999999,\n", - " 0.8228390000000001,\n", - " 0.82405,\n", - " 0.825257,\n", - " 0.826462,\n", - " 0.827671,\n", - " 0.8288780000000001,\n", - " 0.830083,\n", - " 0.831292,\n", - " 0.832505,\n", - " 0.83372,\n", - " 0.834937,\n", - " 0.836156,\n", - " 0.8373700000000001,\n", - " 0.838585,\n", - " 0.839808,\n", - " 0.841029,\n", - " 0.842245,\n", - " 0.843466,\n", - " 0.8446910000000001,\n", - " 0.845921,\n", - " 0.847152,\n", - " 0.848376,\n", - " 0.849597,\n", - " 0.850818,\n", - " 0.852036,\n", - " 0.8532569999999999,\n", - " 0.85448,\n", - " 0.855704,\n", - " 0.8569249999999999,\n", - " 0.858144,\n", - " 0.859367,\n", - " 0.860595,\n", - " 0.8618260000000001,\n", - " 0.8630599999999999,\n", - " 0.864294,\n", - " 0.865525,\n", - " 0.866751,\n", - " 0.8679800000000001,\n", - " 0.869206,\n", - " 0.870435,\n", - " 0.8716630000000001,\n", - " 0.87289,\n", - " 0.87412,\n", - " 0.8753569999999999,\n", - " 0.876599,\n", - " 0.877837,\n", - " 0.879068,\n", - " 0.880294,\n", - " 0.8815230000000001,\n", - " 0.882749,\n", - " 0.88398,\n", - " 0.8852140000000001,\n", - " 0.886445,\n", - " 0.887673,\n", - " 0.8889020000000001,\n", - " 0.89013,\n", - " 0.891359,\n", - " 0.892583,\n", - " 0.893812,\n", - " 0.895044,\n", - " 0.8962730000000001,\n", - " 0.897499,\n", - " 0.898732,\n", - " 0.8999600000000001,\n", - " 0.901183,\n", - " 0.902411,\n", - " 0.903648,\n", - " 0.904882,\n", - " 0.906109,\n", - " 0.9073289999999999,\n", - " 0.908552,\n", - " 0.909785,\n", - " 0.911025,\n", - " 0.912267,\n", - " 0.913511,\n", - " 0.9147569999999999,\n", - " 0.916001,\n", - " 0.917245,\n", - " 0.918494,\n", - " 0.919744,\n", - " 0.92099,\n", - " 0.9222340000000001,\n", - " 0.923476,\n", - " 0.924716,\n", - " 0.925956,\n", - " 0.927203,\n", - " 0.928454,\n", - " 0.929706,\n", - " 0.930954,\n", - " 0.931892,\n", - " 0.933146,\n", - " 0.934396,\n", - " 0.9356420000000001,\n", - " 0.936886,\n", - " 0.9381280000000001,\n", - " 0.939369,\n", - " 0.940615,\n", - " 0.9418630000000001,\n", - " 0.943111,\n", - " 0.944357,\n", - " 0.945603,\n", - " 0.946847,\n", - " 0.948097,\n", - " 0.9493510000000001,\n", - " 0.950603,\n", - " 0.951851,\n", - " 0.953101,\n", - " 0.954357,\n", - " 0.955613,\n", - " 0.9568650000000001,\n", - " 0.958122,\n", - " 0.959382,\n", - " 0.9606380000000001,\n", - " 0.961896,\n", - " 0.963156,\n", - " 0.9644170000000001,\n", - " 0.965675,\n", - " 0.966933,\n", - " 0.968197,\n", - " 0.969458,\n", - " 0.970724,\n", - " 0.971992,\n", - " 0.973259,\n", - " 0.974527,\n", - " 0.975794,\n", - " 0.977062,\n", - " 0.9783310000000001,\n", - " 0.979607,\n", - " 0.9808840000000001,\n", - " 0.982159,\n", - " 0.983433,\n", - " 0.984708,\n", - " 0.985986,\n", - " 0.9872650000000001,\n", - " 0.9885499999999999,\n", - " 0.989831,\n", - " 0.991111,\n", - " 0.99239,\n", - " 0.993669,\n", - " 0.994951,\n", - " 0.996236,\n", - " 0.997527,\n", - " 0.99882,\n", - " 1.00011,\n", - " 1.0014,\n", - " 1.00269,\n", - " 1.00398,\n", - " 1.00527,\n", - " 1.00657,\n", - " 1.00787,\n", - " 1.00917,\n", - " 1.01047,\n", - " 1.01177,\n", - " 1.0130700000000001,\n", - " 1.01437,\n", - " 1.01567,\n", - " 1.0169700000000002,\n", - " 1.01828,\n", - " 1.01959,\n", - " 1.02089,\n", - " 1.0221900000000002,\n", - " 1.0235,\n", - " 1.0248,\n", - " 1.0261099999999999,\n", - " 1.02742,\n", - " 1.0287300000000001,\n", - " 1.03004,\n", - " 1.0313599999999998,\n", - " 1.0326700000000002,\n", - " 1.0339800000000001,\n", - " 1.03529,\n", - " 1.03661,\n", - " 1.03793,\n", - " 1.03924,\n", - " 1.04055,\n", - " 1.0418699999999999,\n", - " 1.04318,\n", - " 1.0445,\n", - " 1.04581,\n", - " 1.0471300000000001,\n", - " 1.04845,\n", - " 1.04977,\n", - " 1.0510899999999999,\n", - " 1.05241,\n", - " 1.05372,\n", - " 1.05504,\n", - " 1.0563699999999998,\n", - " 1.0577,\n", - " 1.05902,\n", - " 1.06035,\n", - " 1.0616700000000001,\n", - " 1.06298,\n", - " 1.0643,\n", - " 1.06562,\n", - " 1.06695,\n", - " 1.06828,\n", - " 1.06928,\n", - " 1.07028,\n", - " 1.07128,\n", - " 1.07229,\n", - " 1.07329,\n", - " 1.07462,\n", - " 1.07562,\n", - " 1.0766300000000002,\n", - " 1.07763,\n", - " 1.0786300000000002,\n", - " 1.0796400000000002,\n", - " 1.08064,\n", - " 1.0816400000000002,\n", - " 1.0826500000000001,\n", - " 1.0836500000000002,\n", - " 1.0846600000000002,\n", - " 1.08566,\n", - " 1.08667,\n", - " 1.0876700000000001,\n", - " 1.08868,\n", - " 1.08969,\n", - " 1.0907,\n", - " 1.09172,\n", - " 1.09273,\n", - " 1.09374,\n", - " 1.09476,\n", - " 1.09577,\n", - " 1.09678,\n", - " 1.0977999999999999,\n", - " 1.09882,\n", - " 1.09985,\n", - " 1.10087,\n", - " 1.10189,\n", - " 1.10291,\n", - " 1.10393,\n", - " 1.10494,\n", - " 1.10596,\n", - " 1.10698,\n", - " 1.108,\n", - " 1.10901,\n", - " 1.11003,\n", - " 1.1110499999999999,\n", - " 1.11207,\n", - " 1.1130799999999998,\n", - " 1.11409,\n", - " 1.1151099999999998,\n", - " 1.11612,\n", - " 1.11714,\n", - " 1.11816,\n", - " 1.11917,\n", - " 1.12019,\n", - " 1.1212,\n", - " 1.1222100000000002,\n", - " 1.12321,\n", - " 1.12422,\n", - " 1.12523,\n", - " 1.1262400000000001,\n", - " 1.12726,\n", - " 1.12828,\n", - " 1.1293,\n", - " 1.13032,\n", - " 1.13133,\n", - " 1.13235,\n", - " 1.13337,\n", - " 1.1343900000000002,\n", - " 1.13541,\n", - " 1.13643,\n", - " 1.13745,\n", - " 1.13846,\n", - " 1.13948,\n", - " 1.1405,\n", - " 1.14152,\n", - " 1.14254,\n", - " 1.14356,\n", - " 1.14458,\n", - " 1.1456,\n", - " 1.14663,\n", - " 1.1476600000000001,\n", - " 1.14869,\n", - " 1.14972,\n", - " 1.15074,\n", - " 1.1517600000000001,\n", - " 1.15279,\n", - " 1.15382,\n", - " 1.15485,\n", - " 1.1558800000000002,\n", - " 1.15691,\n", - " 1.1579300000000001,\n", - " 1.15896,\n", - " 1.15999,\n", - " 1.16102,\n", - " 1.16205,\n", - " 1.16308,\n", - " 1.1641,\n", - " 1.1651300000000002,\n", - " 1.16616,\n", - " 1.16718,\n", - " 1.1682000000000001,\n", - " 1.1692200000000001,\n", - " 1.17025,\n", - " 1.17127,\n", - " 1.17231,\n", - " 1.17334,\n", - " 1.1743800000000002,\n", - " 1.17541,\n", - " 1.17645,\n", - " 1.17748,\n", - " 1.17851,\n", - " 1.1795499999999999,\n", - " 1.18059,\n", - " 1.18162,\n", - " 1.18266,\n", - " 1.1837,\n", - " 1.1847400000000001,\n", - " 1.18578,\n", - " 1.18682,\n", - " 1.18787,\n", - " ...]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "uuid = \"8d6b034e-efb4-427a-a0d3-666e62762cba\"\n", - "item = dsms[uuid]\n", - "\n", - "print(\"Original unit:\", item.dataframe.get(\"Standardkraft\").get_unit())\n", - "\n", - "print(\"\\n\\nNow the converted data column in kN\")\n", - "item.dataframe.get(\"Standardkraft\").convert_to(\"kN\")\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "sdk", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 4a3f1b4cab19957af4a6d4f3bc44ba95462b2cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 20 Aug 2024 09:47:02 +0200 Subject: [PATCH 113/114] update docs for KItem schema --- docs/dsms_sdk/dsms_kitem_schema.md | 130 +++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 33 deletions(-) diff --git a/docs/dsms_sdk/dsms_kitem_schema.md b/docs/dsms_sdk/dsms_kitem_schema.md index f1c21c9..9ab6eec 100644 --- a/docs/dsms_sdk/dsms_kitem_schema.md +++ b/docs/dsms_sdk/dsms_kitem_schema.md @@ -12,21 +12,23 @@ The schema contains complex types and references, indicating an advanced usage s | Field Name | Description | Type | Default | Property Namespace | Required / Optional | |:-----------------:|:--------------------------------------------------------------------------------------------------------:|:-------------------------------------------------:|:--------:|:------------------:|:-----------------:| | Name | Human-readable name of the KItem. | string | Not Applicable | `name` | Required | +| ID | ID of the KItem | Union[UUID, string] | Not Applicable | `id` | Optional | | Slug | A unique slug identifier for the KItem, minimum of 4 characters. | string | `None` | `slug` | Optional | | Ktype ID | The type ID of the KItem | Union[Enum, string] | Not Applicable | `ktype_id` | Required | | Created At | Timestamp of when the KItem was created. | Union[string, datetime] | `None` | `created_at` | Automatically generated | | Updated At | Timestamp of when the KItem was updated. | Union[string, datetime] | `None` | `updated_at` | Automatically generated | +| Avatar | The avatar of the KItem. | Union[[Avatar](#avatar-fields), Dict[str, Any]] | `None` | `avatar` | Optional | | Avatar Exists | Whether the KItem holds an avatar or not. | boolean | `False` | `avatar_exists` | Automatically generated | -| Custom Properties | A set of custom properties related to the KItem. | Any | `{}` | `custom_properties`| Optional | -| Summary | A brief human-readable summary of the KItem | string] | `None` | `summary` | Optional | +| Custom Properties | A set of custom properties related to the KItem. | Any | `None` | `custom_properties`| Optional | +| Summary | A brief human-readable summary of the KItem | string | `None` | `summary` | Optional | | KItem Apps | A list of applications associated with the KItem | List[[App](#app-fields)] | `[ ]` | `kitem_apps` | Optional | | Annotations | A list of annotations related to the KItem | List[[Annotation](#annotation-fields)] | `[ ]` | `annotations` | Optional | | Affiliations | A list of affiliations associated with the KItem | List[[Affiliation](#affiliation-fields)] | `[ ]` | `affiliations` | Optional | | Contacts | Contact information related to the KItem | List[[ContactInfo](#contactinfo-fields)] | `[ ]` | `contacts` | Optional | | External Links | A list of external links related to the KItem | List[[ExternalLink](#externallink-fields)] | `[ ]` | `external_links` | Optional | -| Attachments | A list of file attachments associated with the KItem | List [ Union [[Attachment ](#attachment-fields)], string] | `[ ]` | `attachments` | Optional | +| Attachments | A list of file attachments associated with the KItem | List [Union [[Attachment](#attachment-fields)], string] | `[ ]` | `attachments` | Optional | | Dataframe | Dataframe associated with the KItem, e.g. a time series | Union[List[[Column](#column-fields)], pd.DataFrame, Dictionary[string, Union[List, Dictionary]]] | `None` | `dataframe` | Optional | -| Linked KItems | List of other KItems linked to this KItem | List[Union[[LinkedKItem](#linkedkitem-fields), "KItem"]] | `None` | `linked_kitems` | Optional | +| Linked KItems | List of other KItems linked to this KItem | List[Union[[LinkedKItem](#linkedkitem-fields), "KItem"]] | `[ ]` | `linked_kitems` | Optional | | User Groups | User groups with access to this KItem | List[[UserGroup](#usergroup-fields)] | `[ ]` | `user_groups` | Optional | ### Example Usage @@ -38,14 +40,26 @@ item = KItem( ktype_id="Testing Machine", custom_properties={"location": "Room01", "max_force": "100Pa"}, summary="This is a summary", - kitem_apps=[{"executable": "my_analysis", "title": "Analysis", "description": "analysis the tensile strength from machine data"}], + kitem_apps=[ + {"executable": "my_analysis_file", + "title": "Analysis", + "description": "Analysis the tensile strength from machine data"} + ], annotations=["http://example.org/sample_kitem/annotation"], - affiliations=[{"name": "Institute ABC"}], - contacts=[{"name": "John Doe", "email": "john.doe@example.com"}], - external_links=[{"label": "Project Website", "url": "https://example.com"}], + affiliations=[ + {"name": "Institute ABC"} + ], + contacts=[ + {"name": "John Doe", "email": "john.doe@example.com"} + ], + external_links=[ + {"label": "Project Website", "url": "https://example.com"} + ], attachments=["research_data.csv"], - linked_kitems=[{"id": "{kitem_id_of_present}", "source_id": "{kitem_id_of_source}"}], - user_groups=[{"group_id": "{other_kitem_id}", "name": "DigiMaterials"}] + linked_kitems=[another_kitem], + user_groups=[ + {"group_id": "33305", "name": "DigiMaterials"} + ] ) ``` @@ -54,26 +68,50 @@ item = KItem( | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| KItem App ID | ID of the KItem App | integer | `None` | `kitem_app_id` | Automatically generated | | Executable | Name of the executable | string | `None` | `executable` | Required | | Title | Title of the application | string | `None` | `title` | Required | | Description | Description of the application | string | `None` | `description` |Required | +| Tags | Tags related to the application | Dict | `None` | `tags` | `tags` | Optional | +| Additional properties | Additional properties related to the application | [Additional Properties](#additional-properties-fields) | `None` | `additional_properties` | Optional | ### Example Usage ```python -sample_kitem.kitem_apps = { +sample_kitem.kitem_apps = [{ "executable": "my_application", "title": "My Application", "description": "My Application for analysis.", -} +}] +``` + +## Additional Properties Fields + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| Trigger Upon Upload | Whether the application is triggered when an attachment is uploaded | boolean | `False` | `triggerUponUpload` | Optional | +| Trigger Upon Extension | File extensions for which the upload shall be triggered | List[string] | `None` | `triggerUponUploadFileExtensions` | Optional | + +### Example Usage +```python +item.kitem_apps = [ + { + "executable": "my_yaml_file", + "title": "Data2RDF", + "additional_properties": { + "triggerUponUpload": True, + "triggerUponUploadFileExtensions": [".csv"], + }, + } + ] ``` ## Annotation Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| -| IRI | IRI of the annotation | string | `None` | `iri` | Required | -| Name | Name of the annotation | string | `None` | `name` | Required | -| Namespace | Namespace of the annotation | string | `None` | `namespace` | Required | +| IRI | IRI of the annotation | string | Not Applicable | `iri` | Required | +| Name | Name of the annotation | string | Not Applicable | `name` | Required | +| Namespace | Namespace of the annotation | string | Not Applicable | `namespace` | Required | ### Example Usage ```python @@ -95,43 +133,64 @@ sample_kitem.annotations = [ | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| -| Name | Name of the affiliation | string | `None` | `name` | Required | +| Name | Name of the affiliation | string | Not Applicable | `name` | Required | ### Example Usage ```python -sample_kitem.affiliations = {"name": "Research BAC"} +sample_kitem.affiliations = [{"name": "Research BAC"}] ``` +## Avatar Fields + +| Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | +| :-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| +| File | The file path to the image or PIL.Image object when setting a new avatar is set | Union[string, PIL.Image] | `None` | `file` | Optional | +| Include QR code | Include QR code in the image | bool | `False` | `include_qr` | Optional | + +### Example Usage +```python +sample_kitem.avatars = [ + { + "file": "my_avatar.jpg", + "include_qr": True + } +] +``` ## ContactInfo Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:-------------:|:-------:|:------------------:|:-----------------:| -| Email | Email of the contact person | string | `None` | `email` | Required | -| Name | Name of the contact person | string | `None` | `name` | Required | +| Email | Email of the contact person | string | Not Applicable | `email` | Required | +| Name | Name of the contact person | string | Not Applicable | `name` | Required | | User Id | User ID of the contact person | string (UUID) | `None` | `user_id` | Optional | ### Example Usage ```python -sample_kitem.contacts = { - "email": "research.abc@gmail.com", - "name": "project01@research.abc.de","user_id":"33f24ee5-2f03-4874-854d-388af782c4c3" -} +sample_kitem.contacts = [ + { + "email": "research.abc@gmail.com", + "name": "project01@research.abc.de", + "user_id":"33f24ee5-2f03-4874-854d-388af782c4c3" + } +] ``` ## ExternalLink Fields | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------------------------:|:-------:|:------------------:|:-----------------:| -| Label | Label of the external link | string | `None` | `label` | Required | -| Url | URL of the external link | string , format: URI, minLength: 1 | `None` | `url` | Required | +| Label | Label of the external link | string | Not Applicable | `label` | Required | +| Url | URL of the external link | string , format: URI, minLength: 1 | Not Applicable | `url` | Required | ### Example Usage ```python -sample_kitem.external_links = [{ - "label": "project link", - "url": "www.projectmachine01.com" -}] +sample_kitem.external_links = [ + { + "label": "project link", + "url": "www.projectmachine01.com" + } +] ``` @@ -139,7 +198,7 @@ sample_kitem.external_links = [{ | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| -| Name | File name of the attachment | string | `None` | `name` | Required | +| Name | File name of the attachment | string | Not Applicable | `name` | Required | | Content | Content of the attachment | string | `None` | `content` | Optional | ### Example Usage @@ -160,7 +219,8 @@ sample_kitem.attachments = [ | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | |:-----------------:|:---------------------------------:|:--------:|:-------:|:------------------:|:-----------------:| -| Name | File name of the attachment | string | `None` | `name` | Required | +| Name | Name of the column | string | Not Applicable | `name` | Required | +| Column ID | ID of the column | integer | Not Applicable | `column_id` | Required | ### Example Usage ```python @@ -183,11 +243,15 @@ sample_kitem.dataframe = { ```python sample_kitem.linked_kitems = [ { - "id": "33305", - "source_id": "22205" + "id": "3e894d2c-d1a5-42ca-b6e2-cbbc09e0e686", # id of the target KItem } ] ``` +```python +sample_kitem.linked_kitems = [ + another_kitem +] +``` ## UserGroup Fields From 71014e8cf54f694037e1c3d09d6f1dfc8709af87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20B=C3=BCschelberger?= Date: Tue, 20 Aug 2024 10:07:30 +0200 Subject: [PATCH 114/114] update tutortials and add readtherdocs config --- .readthedocs.yml | 24 + docs/dsms_sdk/tutorials/4_deletion.ipynb | 2 - examples/basic_usage.ipynb | 189 ++-- examples/create_and_run_app.py | 187 ---- examples/tutorials/1_introduction.ipynb | 138 +++ examples/tutorials/2_creation.ipynb | 330 +++++++ examples/tutorials/3_updation.ipynb | 287 ++++++ examples/tutorials/4_deletion.ipynb | 404 +++++++++ examples/tutorials/5_search.ipynb | 481 ++++++++++ examples/tutorials/6_apps.ipynb | 1025 ++++++++++++++++++++++ examples/tutorials/testfile.txt | 1 + 11 files changed, 2807 insertions(+), 261 deletions(-) create mode 100644 .readthedocs.yml delete mode 100644 examples/create_and_run_app.py create mode 100644 examples/tutorials/1_introduction.ipynb create mode 100644 examples/tutorials/2_creation.ipynb create mode 100644 examples/tutorials/3_updation.ipynb create mode 100644 examples/tutorials/4_deletion.ipynb create mode 100644 examples/tutorials/5_search.ipynb create mode 100644 examples/tutorials/6_apps.ipynb create mode 100644 examples/tutorials/testfile.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..007bac7 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,24 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +sphinx: + configuration: docs/conf.py + +formats: + - pdf + - epub + +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/docs/dsms_sdk/tutorials/4_deletion.ipynb b/docs/dsms_sdk/tutorials/4_deletion.ipynb index e5ecf72..fe44b17 100644 --- a/docs/dsms_sdk/tutorials/4_deletion.ipynb +++ b/docs/dsms_sdk/tutorials/4_deletion.ipynb @@ -17,8 +17,6 @@ "\n", "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, alongwith with installation of this package and have establised access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", - "Now let us import the needed classes and functions for this tutorial.\n", - "\n", "Now let us import the needed classes and functions for this tutorial." ] }, diff --git a/examples/basic_usage.ipynb b/examples/basic_usage.ipynb index 97d5c31..d66b53c 100644 --- a/examples/basic_usage.ipynb +++ b/examples/basic_usage.ipynb @@ -124,13 +124,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", + "\tid = 617cd64f-1a4d-48d5-aed6-ff9d9e3ddb20, \n", "\n", "\tktype_id = KTypes.Dataset, \n", "\n", "\tin_backend = False, \n", "\n", - "\tslug = foo123-12a798aa, \n", + "\tslug = foo123-617cd64f, \n", "\n", "\tannotations = [], \n", "\n", @@ -198,7 +198,7 @@ { "data": { "text/plain": [ - "'https://bue.materials-data.space/knowledge/dataset/foo123-12a798aa'" + "'https://bue.materials-data.space/knowledge/dataset/foo123-617cd64f'" ] }, "execution_count": 5, @@ -230,13 +230,13 @@ "\n", "\tname = foo123, \n", "\n", - "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", + "\tid = 617cd64f-1a4d-48d5-aed6-ff9d9e3ddb20, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-12a798aa, \n", + "\tslug = foo123-617cd64f, \n", "\n", "\tannotations = [], \n", "\n", @@ -256,9 +256,9 @@ "\n", "\tcontacts = [], \n", "\n", - "\tcreated_at = 2024-08-09 15:05:38.343459, \n", + "\tcreated_at = 2024-08-20 08:01:50.536406, \n", "\n", - "\tupdated_at = 2024-08-09 15:05:38.343459, \n", + "\tupdated_at = 2024-08-20 08:01:50.536406, \n", "\n", "\texternal_links = [], \n", "\n", @@ -352,13 +352,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", + "\tid = 617cd64f-1a4d-48d5-aed6-ff9d9e3ddb20, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-12a798aa, \n", + "\tslug = foo123-617cd64f, \n", "\n", "\tannotations = [\n", "\t\t{\n", @@ -399,9 +399,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-08-09 15:05:38.343459, \n", + "\tcreated_at = 2024-08-20 08:01:50.536406, \n", "\n", - "\tupdated_at = 2024-08-09 15:05:43.866336, \n", + "\tupdated_at = 2024-08-20 08:01:54.745601, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -499,7 +499,6 @@ "\n", "For the basic usage, please have a look on the Jupyter Notebook under `examples/basic_usage.ipynb`. This tutorial provides a basic overview of using the dsms package to interact with Knowledge Items.\n", "\n", - "\n", "## Disclaimer\n", "\n", "Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM.\n", @@ -617,13 +616,13 @@ "\n", "\tname = foobar, \n", "\n", - "\tid = 12a798aa-3193-4963-a00d-677b1d0a7197, \n", + "\tid = 617cd64f-1a4d-48d5-aed6-ff9d9e3ddb20, \n", "\n", "\tktype_id = dataset, \n", "\n", "\tin_backend = True, \n", "\n", - "\tslug = foo123-12a798aa, \n", + "\tslug = foo123-617cd64f, \n", "\n", "\tannotations = [], \n", "\n", @@ -653,9 +652,9 @@ "\t\t}\n", "\t], \n", "\n", - "\tcreated_at = 2024-08-09 15:05:38.343459, \n", + "\tcreated_at = 2024-08-20 08:01:50.536406, \n", "\n", - "\tupdated_at = 2024-08-09 15:05:43.866336, \n", + "\tupdated_at = 2024-08-20 08:01:54.745601, \n", "\n", "\texternal_links = [\n", "\t\t{\n", @@ -790,13 +789,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 85d47b16-0fff-40b7-9574-c05a79a0dc0e, \n", + " \tid = 966beb55-6eb5-422e-b8c1-84d65b8cf50d, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-85d47b16, \n", + " \tslug = foo1-966beb55, \n", " \n", " \tannotations = [], \n", " \n", @@ -804,9 +803,9 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", + " \t\t\tid: 3ca3c293-8845-4f0e-afcb-6c680c07239f\n", " \t\t\tname: foo 2\n", - " \t\t\tslug: foo2-21146fbf\n", + " \t\t\tslug: foo2-3ca3c293\n", " \t\t\tktype_id: organization\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", @@ -817,7 +816,7 @@ " \t\t\tdescription: None\n", " \t\t}]\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", + " \t\t\tid: 966beb55-6eb5-422e-b8c1-84d65b8cf50d\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -828,8 +827,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-09T15:06:00.156328\n", - " \t\t\tupdated_at: 2024-08-09T15:06:00.156328\n", + " \t\t\tcreated_at: 2024-08-20T08:02:09.024038\n", + " \t\t\tupdated_at: 2024-08-20T08:02:09.024038\n", " \t\t\n", " \t], \n", " \n", @@ -845,9 +844,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:05:59.532610, \n", + " \tcreated_at = 2024-08-20 08:02:08.491699, \n", " \n", - " \tupdated_at = 2024-08-09 15:05:59.532610, \n", + " \tupdated_at = 2024-08-20 08:02:08.491699, \n", " \n", " \texternal_links = [], \n", " \n", @@ -893,13 +892,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 85d47b16-0fff-40b7-9574-c05a79a0dc0e, \n", + " \tid = 966beb55-6eb5-422e-b8c1-84d65b8cf50d, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-85d47b16, \n", + " \tslug = foo1-966beb55, \n", " \n", " \tannotations = [], \n", " \n", @@ -907,9 +906,9 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", + " \t\t\tid: 3ca3c293-8845-4f0e-afcb-6c680c07239f\n", " \t\t\tname: foo 2\n", - " \t\t\tslug: foo2-21146fbf\n", + " \t\t\tslug: foo2-3ca3c293\n", " \t\t\tktype_id: organization\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", @@ -920,7 +919,7 @@ " \t\t\tdescription: None\n", " \t\t}]\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", + " \t\t\tid: 966beb55-6eb5-422e-b8c1-84d65b8cf50d\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -931,8 +930,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-09T15:06:00.156328\n", - " \t\t\tupdated_at: 2024-08-09T15:06:00.156328\n", + " \t\t\tcreated_at: 2024-08-20T08:02:09.024038\n", + " \t\t\tupdated_at: 2024-08-20T08:02:09.024038\n", " \t\t\n", " \t], \n", " \n", @@ -948,9 +947,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:05:59.532610, \n", + " \tcreated_at = 2024-08-20 08:02:08.491699, \n", " \n", - " \tupdated_at = 2024-08-09 15:05:59.532610, \n", + " \tupdated_at = 2024-08-20 08:02:08.491699, \n", " \n", " \texternal_links = [], \n", " \n", @@ -970,13 +969,13 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65, \n", + " \tid = 3ca3c293-8845-4f0e-afcb-6c680c07239f, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo2-21146fbf, \n", + " \tslug = foo2-3ca3c293, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -991,15 +990,15 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", + " \t\t\tid: 966beb55-6eb5-422e-b8c1-84d65b8cf50d\n", " \t\t\tname: foo 1\n", - " \t\t\tslug: foo1-85d47b16\n", + " \t\t\tslug: foo1-966beb55\n", " \t\t\tktype_id: dataset-catalog\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", " \t\t\tannotations: []\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", + " \t\t\tid: 3ca3c293-8845-4f0e-afcb-6c680c07239f\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -1010,8 +1009,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-09T15:05:59.532610\n", - " \t\t\tupdated_at: 2024-08-09T15:05:59.532610\n", + " \t\t\tcreated_at: 2024-08-20T08:02:08.491699\n", + " \t\t\tupdated_at: 2024-08-20T08:02:08.491699\n", " \t\t\n", " \t], \n", " \n", @@ -1027,9 +1026,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:06:00.156328, \n", + " \tcreated_at = 2024-08-20 08:02:09.024038, \n", " \n", - " \tupdated_at = 2024-08-09 15:06:00.156328, \n", + " \tupdated_at = 2024-08-20 08:02:09.024038, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1049,13 +1048,13 @@ " \n", " \tname = foo 3, \n", " \n", - " \tid = 67b425d5-edfa-4d55-8294-d060bfdba13a, \n", + " \tid = 31a58a53-8f50-4f18-93ee-90ff5a806e14, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo3-67b425d5, \n", + " \tslug = foo3-31a58a53, \n", " \n", " \tannotations = [], \n", " \n", @@ -1075,9 +1074,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:06:00.875518, \n", + " \tcreated_at = 2024-08-20 08:02:09.532080, \n", " \n", - " \tupdated_at = 2024-08-09 15:06:00.875518, \n", + " \tupdated_at = 2024-08-20 08:02:09.532080, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1097,13 +1096,13 @@ " \n", " \tname = foo 4, \n", " \n", - " \tid = 752ae260-d833-4b92-a99e-2b43c4ae3662, \n", + " \tid = 552ab1b9-64df-4343-9e4e-e5c292c3999f, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo4-752ae260, \n", + " \tslug = foo4-552ab1b9, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1130,9 +1129,57 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:06:01.576551, \n", + " \tcreated_at = 2024-08-20 08:02:10.062595, \n", + " \n", + " \tupdated_at = 2024-08-20 08:02:10.062595, \n", + " \n", + " \texternal_links = [], \n", + " \n", + " \tkitem_apps = [], \n", + " \n", + " \tsummary = None, \n", + " \n", + " \tuser_groups = [], \n", + " \n", + " \tcustom_properties = None, \n", + " \n", + " \tdataframe = None, \n", + " \n", + " \trdf_exists = False\n", + " ), fuzzy=False),\n", + " SearchResult(hit=KItem(\n", + " \n", + " \tname = Research Institute ABC, \n", + " \n", + " \tid = 21aa50c3-5ec2-4ac3-aba8-69071a4287e2, \n", + " \n", + " \tktype_id = organization, \n", + " \n", + " \tin_backend = True, \n", + " \n", + " \tslug = researchinstituteabc-21aa50c3, \n", + " \n", + " \tannotations = [], \n", + " \n", + " \tattachments = [], \n", + " \n", + " \tlinked_kitems = [], \n", + " \n", + " \taffiliations = [], \n", + " \n", + " \tauthors = [\n", + " \t\t{\n", + " \t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + " \t\t}\n", + " \t], \n", + " \n", + " \tavatar_exists = False, \n", + " \n", + " \tcontacts = [], \n", + " \n", + " \tcreated_at = 2024-08-19 18:26:00.740761, \n", " \n", - " \tupdated_at = 2024-08-09 15:06:01.576551, \n", + " \tupdated_at = 2024-08-19 18:26:00.740761, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1178,13 +1225,13 @@ " \n", " \tname = foo 1, \n", " \n", - " \tid = 85d47b16-0fff-40b7-9574-c05a79a0dc0e, \n", + " \tid = 966beb55-6eb5-422e-b8c1-84d65b8cf50d, \n", " \n", " \tktype_id = dataset-catalog, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo1-85d47b16, \n", + " \tslug = foo1-966beb55, \n", " \n", " \tannotations = [], \n", " \n", @@ -1192,9 +1239,9 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", + " \t\t\tid: 3ca3c293-8845-4f0e-afcb-6c680c07239f\n", " \t\t\tname: foo 2\n", - " \t\t\tslug: foo2-21146fbf\n", + " \t\t\tslug: foo2-3ca3c293\n", " \t\t\tktype_id: organization\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", @@ -1205,7 +1252,7 @@ " \t\t\tdescription: None\n", " \t\t}]\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", + " \t\t\tid: 966beb55-6eb5-422e-b8c1-84d65b8cf50d\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -1216,8 +1263,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-09T15:06:00.156328\n", - " \t\t\tupdated_at: 2024-08-09T15:06:00.156328\n", + " \t\t\tcreated_at: 2024-08-20T08:02:09.024038\n", + " \t\t\tupdated_at: 2024-08-20T08:02:09.024038\n", " \t\t\n", " \t], \n", " \n", @@ -1233,9 +1280,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:05:59.532610, \n", + " \tcreated_at = 2024-08-20 08:02:08.491699, \n", " \n", - " \tupdated_at = 2024-08-09 15:05:59.532610, \n", + " \tupdated_at = 2024-08-20 08:02:08.491699, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1281,13 +1328,13 @@ " \n", " \tname = foo 2, \n", " \n", - " \tid = 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65, \n", + " \tid = 3ca3c293-8845-4f0e-afcb-6c680c07239f, \n", " \n", " \tktype_id = organization, \n", " \n", " \tin_backend = True, \n", " \n", - " \tslug = foo2-21146fbf, \n", + " \tslug = foo2-3ca3c293, \n", " \n", " \tannotations = [\n", " \t\t{\n", @@ -1302,15 +1349,15 @@ " \n", " \tlinked_kitems = [\n", " \t\t\n", - " \t\t\tid: 85d47b16-0fff-40b7-9574-c05a79a0dc0e\n", + " \t\t\tid: 966beb55-6eb5-422e-b8c1-84d65b8cf50d\n", " \t\t\tname: foo 1\n", - " \t\t\tslug: foo1-85d47b16\n", + " \t\t\tslug: foo1-966beb55\n", " \t\t\tktype_id: dataset-catalog\n", " \t\t\tsummary: None\n", " \t\t\tavatar_exists: False\n", " \t\t\tannotations: []\n", " \t\t\tlinked_kitems: [{\n", - " \t\t\tid: 21146fbf-aa88-4ed1-8b20-c7fa1dc4de65\n", + " \t\t\tid: 3ca3c293-8845-4f0e-afcb-6c680c07239f\n", " \t\t}]\n", " \t\t\texternal_links: []\n", " \t\t\tcontacts: []\n", @@ -1321,8 +1368,8 @@ " \t\t\tattachments: []\n", " \t\t\tuser_groups: []\n", " \t\t\tcustom_properties: None\n", - " \t\t\tcreated_at: 2024-08-09T15:05:59.532610\n", - " \t\t\tupdated_at: 2024-08-09T15:05:59.532610\n", + " \t\t\tcreated_at: 2024-08-20T08:02:08.491699\n", + " \t\t\tupdated_at: 2024-08-20T08:02:08.491699\n", " \t\t\n", " \t], \n", " \n", @@ -1338,9 +1385,9 @@ " \n", " \tcontacts = [], \n", " \n", - " \tcreated_at = 2024-08-09 15:06:00.156328, \n", + " \tcreated_at = 2024-08-20 08:02:09.024038, \n", " \n", - " \tupdated_at = 2024-08-09 15:06:00.156328, \n", + " \tupdated_at = 2024-08-20 08:02:09.024038, \n", " \n", " \texternal_links = [], \n", " \n", @@ -1418,9 +1465,7 @@ " AppConfig(name=excel_notched_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", " AppConfig(name=excel_shear_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", " AppConfig(name=excel_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", - " AppConfig(name=ternary-plot, specification={'metadata': {'generateName': 'ckan-tenary-app-'}}),\n", - " AppConfig(name=testapp, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", - " AppConfig(name=testapp2, specification={'metadata': {'generateName': 'data2rdf-'}})]" + " AppConfig(name=ternary-plot, specification={'metadata': {'generateName': 'ckan-tenary-app-'}})]" ] }, "execution_count": 23, diff --git a/examples/create_and_run_app.py b/examples/create_and_run_app.py deleted file mode 100644 index 00e7e75..0000000 --- a/examples/create_and_run_app.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Tutorial for creating and running apps""" - -import time - -from dsms import DSMS, AppConfig, KItem - -# Connect to DSMS -print("\nConnect to DSMS") -dsms = DSMS(env="../.env") - -# Define app name and item name -configname = "testapp2" -kitem_name = "test item" - -# Define data -data = """A,B,C -1.2,1.3,1.5 -1.7,1.8,1.9 -2.0,2.1,2.3 -2.5,2.6,2.8 -3.0,3.2,3.4 -3.6,3.7,3.9 -4.1,4.3,4.4 -4.7,4.8,5.0 -5.2,5.3,5.5 -5.8,6.0,6.1 -""" - -extension = ".csv" - -# Define app parameters -parameters = [ - {"name": "parser", "value": "csv"}, - {"name": "time_series_header_length", "value": 1}, - {"name": "metadata_length", "value": 0}, - {"name": "metadata_sep", "value": ","}, - {"name": "time_series_sep", "value": ","}, - {"name": "log_level", "value": "DEBUG"}, - { - "name": "mapping", - "value": """ - [ - { - "key": "A", - "iri": "https://w3id.org/steel/ProcessOntology/TestTime", - "unit": "s" - }, - { - "key": "B", - "iri": "https://w3id.org/steel/ProcessOntology/StandardForce", - "unit": "kN" - }, - { - "key": "C", - "iri": "https://w3id.org/steel/ProcessOntology/AbsoluteCrossheadTravel", - "unit": "mm" - } - ] - """, - }, -] - -# Define app specification -specification = { - "apiVersion": "argoproj.io/v1alpha1", - "kind": "Workflow", - "metadata": {"generateName": "data2rdf-"}, - "spec": { - "entrypoint": "execute_pipeline", - "workflowTemplateRef": {"name": "dsms-data2rdf"}, - "arguments": {"parameters": parameters}, - }, -} - -# Create app specification -print("\nCreate app specification") -appspec = AppConfig( - name=configname, - specification=specification, # this can also be a file path instead of a dict -) - -# Create kitem -print("\nCreate kitem") -item = KItem( - name=kitem_name, - ktype_id=dsms.ktypes.Dataset, - kitem_apps=[ - { - "executable": appspec.name, - "title": "data2rdf", - "additional_properties": { - "triggerUponUpload": True, - "triggerUponUploadFileExtensions": [extension], - }, - } - ], - avatar={"include_qr": True}, -) - -# Commit KItem -print("\nCommit KItem") -dsms.commit() - -# Add attachment -# Here we are setting the content directly, -# but item.attachments can also be a list with a filepath -# e.g. item.attachments = ["path/to/my/file.csv"] -print("\nAdd attachment") -item.attachments = [{"name": "dummy_data.csv", "content": data}] - -# Verify that the attachment was added -print("\nVerify that the attachment was added") -print(item) - -# Upload attachment and trigger app -print("\nUpload attachment and trigger app") -dsms.commit() - -# Get dataframe -print("\nGet dataframe") -print(item.dataframe.StandardForce.convert_to("N")) - -# Verify that the dataframe was deleted -item.dataframe = {} -dsms.commit() -print("\nVerify that the dataframe was deleted") -print(item) - -# Run pipeline manually -print("\nRun pipeline manually") -job = item.kitem_apps.by_title["data2rdf"].run( - attachment_name=item.attachments[0].name, set_token=True, set_host_url=True -) - -# See job status -print("\nSee job status") -print(job.status) -print("\n See job logs:") -print(job.logs) - -# Get dataframe again -print("\nGet dataframe again") -print(item.dataframe.StandardForce.convert_to("N")) - -# Delete dataframe -print("\nDelete dataframe") -item.dataframe = {} -dsms.commit() - -# Verify that the dataframe was deleted again -print("\nVerify that the dataframe was deleted again") -print(item) - -# Run pipeline manually in the background -print("\nRun pipeline manually in the background") -job = item.kitem_apps.by_title["data2rdf"].run( - attachment_name=item.attachments[0].name, - set_token=True, - set_host_url=True, - wait=False, -) - -# Monitor job status -print("\nMonitor job status") -while True: - time.sleep(1) - print("\n Current status:") - print(job.status) - print("\n Current logs:") - print(job.logs) - if job.status.phase != "Running": - break - -# Reload item -print("\nReload item") -item.refresh() - -# Get dataframe again -print("\nGet dataframe again") -print(item.dataframe.StandardForce.convert_to("N")) - -# Cleanup -print("\nCleanup") -del dsms[item] -del dsms[appspec] - -dsms.commit() diff --git a/examples/tutorials/1_introduction.ipynb b/examples/tutorials/1_introduction.ipynb new file mode 100644 index 0000000..a727932 --- /dev/null +++ b/examples/tutorials/1_introduction.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Connecting with the SDK to DSMS\n", + "\n", + "In this tutorial we see the overview on how to setup and basic use DSMS-SDK\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1. Setting up" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\"../../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2. Introduction to KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see which kind of DSMS-object we own as a user (in the beginning, we own none):" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate what a KItem needs in order to be created. KItems are entirely based on Pydantic-Models (v2), hence the properties (in Pydantic called Fields) are automatically validated once we set them.\n", + "\n", + "The schema of the KItem itself is a JSON schema which is machine-readable and can be directly incorporated into Swagger-supported APIs like e.g. FastAPI.\n", + "\n", + "We can investigate the KTypes defined in the remote instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KTypes.Organization\n", + "KTypes.App\n", + "KTypes.Dataset\n", + "KTypes.DatasetCatalog\n", + "KTypes.Expert\n", + "KTypes.Test\n", + "KTypes.Specimen\n", + "KTypes.Batch\n", + "KTypes.Resource\n", + "KTypes.TestingMachine\n" + ] + } + ], + "source": [ + "for ktype in dsms.ktypes:\n", + " print(ktype)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/2_creation.ipynb b/examples/tutorials/2_creation.ipynb new file mode 100644 index 0000000..725bb64 --- /dev/null +++ b/examples/tutorials/2_creation.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Create KItems with the SDK\n", + "\n", + "In this tutorial we see how to create new Kitems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1. Setting up\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\"../../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 2.2: Create KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can make new KItems by simple class-initiation: (Make sure existing KItems are not given as input). \n", + "#" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = KTypes.TestingMachine, \n", + "\n", + "\tin_backend = False, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = None, \n", + "\n", + "\tupdated_at = None, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item = KItem(\n", + " name=\"Machine-1\",\n", + " ktype_id=dsms.ktypes.TestingMachine,\n", + " custom_properties={\"Producer\": \"TestingLab GmBH\",\n", + " \"Location\": \"A404\",\n", + " \"Model Number\" : \"Bending Test Machine No 777\"\n", + " },\n", + ")\n", + "\n", + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Remember: changes are only syncronized with the DSMS when you call the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'https://bue.materials-data.space/knowledge/testing-machine/machine-1-dd091666'" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.commit()\n", + "item.url" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the object we created before running the `commit`-method has automatically been updated, e.g. with the creation- and update-timestamp. We can check this with the below command:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To just get the name of the item, we can do it as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Machine-1'" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item.name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To check the type of the item newly created we can use the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dsms.knowledge.kitem.KItem" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now you can check if the particular kitem is in the list of KItems. This can be done either by using the command:\n", + " `\n", + " dsms.kitems\n", + " `\n", + " or by logging into the frontend dsms instance." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/3_updation.ipynb b/examples/tutorials/3_updation.ipynb new file mode 100644 index 0000000..3bf4d84 --- /dev/null +++ b/examples/tutorials/3_updation.ipynb @@ -0,0 +1,287 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Updating KItems with the SDK\n", + "\n", + "In this tutorial we see how to update existing Kitems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.1. Setting up\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\"../../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now lets get the kitem we created in the [2nd tutorial : Creation of Kitems](2_creation.ipynb)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Machine-1\n" + ] + } + ], + "source": [ + "item = dsms.kitems[-1]\n", + "print(item.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. Updating Kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we would like to update the properties of our KItem we created previously.\n", + "\n", + "Depending on the schema of each property (see [DSMS KItem Schema](../dsms_kitem_schema.md)), we can simply use the standard `list`-method as we know them from basic Python (e.g. for the `annotations`, `attachments`, `external_link`, etc). \n", + "\n", + "Other properties which are not `list`-like can be simply set by attribute-assignment (e.g. `name`, `slug`, `ktype_id`, etc)." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "item.name = \"Machine-1\"\n", + "item.custom_properties.Producer = \"Machinery GmBH\"\n", + "item.attachments.append(\"testfile.txt\")\n", + "item.annotations.append(\"www.machinery.org/\")\n", + "item.external_links.append(\n", + " {\"url\": \"http://machine.org\", \"label\": \"machine-link\"}\n", + ")\n", + "item.contacts.append({\"name\": \"machinesupport\", \"email\": \"machinesupport@group.mail\"})\n", + "item.affiliations.append(\"machine-team\")\n", + "item.user_groups.append({\"name\": \"machinegroup\", \"group_id\": \"123\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see now that the local system path of the attachment is changed to a simply file name, which means that the upload was successful. If not so, an error would have been thrown during the `commit`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see the updates when we print the item:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.machinery.org/,\n", + "\t\t\tname: ,\n", + "\t\t\tnamespace: www.machinery.org,\n", + "\t\t\tdescription: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: testfile.txt\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: machine-team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: machinesupport,\n", + "\t\t\temail: machinesupport@group.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: machine-link,\n", + "\t\t\turl: http://machine.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [\n", + "\t\t{\n", + "\t\t\tname: machinegroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: Machinery GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Furthermore we can also download the file we uploaded again:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t\t\t Downloaded file: {\n", + "\t\t\tname: testfile.txt\n", + "\t\t}\n", + "|------------------------------------Beginning of file------------------------------------|\n", + "This is a calibration protocol!\n", + "|---------------------------------------End of file---------------------------------------|\n" + ] + } + ], + "source": [ + "for file in item.attachments:\n", + " download = file.download()\n", + "\n", + " print(\"\\t\\t\\t Downloaded file:\", file)\n", + " print(\"|------------------------------------Beginning of file------------------------------------|\")\n", + " print(download)\n", + " print(\"|---------------------------------------End of file---------------------------------------|\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/4_deletion.ipynb b/examples/tutorials/4_deletion.ipynb new file mode 100644 index 0000000..210df6b --- /dev/null +++ b/examples/tutorials/4_deletion.ipynb @@ -0,0 +1,404 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Deleting KItems with the SDK\n", + "\n", + "In this tutorial we see how to delete new Kitems and their properties." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.1. Setting up\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\"../../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then lets see the Kitem we are interested in to remove." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.machinery.org/,\n", + "\t\t\tname: ,\n", + "\t\t\tnamespace: www.machinery.org,\n", + "\t\t\tdescription: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: testfile.txt\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: machine-team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: machinesupport,\n", + "\t\t\temail: machinesupport@group.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: machine-link,\n", + "\t\t\turl: http://machine.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [\n", + "\t\t{\n", + "\t\t\tname: machinegroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: Machinery GmBH, \n", + "\t\tLocation: A404, \n", + "\t\tModel Number: Bending Test Machine No 777\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n" + ] + } + ], + "source": [ + "item = dsms.kitems[-1]\n", + "print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2. Deletion of KItems and their properties" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also remove properties from the KItem without deleting the KItem itself.\n", + "\n", + "For the `list`-like properties, we can use the standard `list`-methods from basic Python again (e.g. `pop`, `remove`, etc. or the `del`-operator).\n", + "\n", + "For the other, non-`list`-like properties, we can simply use the attribute-assignment again.\n", + "\n", + "When we only want single parts of the properties in the KItem, we can do it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{\n", + "\t\t\tname: machinegroup,\n", + "\t\t\tgroup_id: 123\n", + "\t\t}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item.attachments.pop(0)\n", + "item.annotations.pop(0)\n", + "item.external_links.pop(0)\n", + "item.contacts.pop(0)\n", + "item.user_groups.pop(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we can also reset the entire property by setting it to e.g. an empty list again:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "item.affiliations = []" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can delete the custom properties by setting the property to an empty dict:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "item.custom_properties = {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Send the changes to the DSMS with the `commit`-method:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-dd091666, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [\n", + "\t\t{\n", + "\t\t\tname: machine-team\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [\n", + "\t\t{\n", + "\t\t\tname: machinesupport,\n", + "\t\t\temail: machinesupport@group.mail,\n", + "\t\t\tuser_id: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tcreated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\tupdated_at = 2024-08-19 18:12:11.338394, \n", + "\n", + "\texternal_links = [\n", + "\t\t{\n", + "\t\t\tlabel: machine-link,\n", + "\t\t\turl: http://machine.org/\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tid: dd091666-a7c9-4b3b-8832-910bdec5c63c, \n", + "\t\tcontent: {}\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we can also delete the whole KItem from the DSMS by applying the `del`-operator to the `dsms`-object with the individual `KItem`-object:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Commit the changes:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now to check if the particular kitem was removed, we can do this by using the command:\n", + " `\n", + " dsms.kitems\n", + " `\n", + " or by logging into the frontend dsms instance." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/5_search.ipynb b/examples/tutorials/5_search.ipynb new file mode 100644 index 0000000..eae274a --- /dev/null +++ b/examples/tutorials/5_search.ipynb @@ -0,0 +1,481 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 5. Searching KItems with the SDK\n", + "\n", + "In this tutorial we see how to search existing Kitems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.1. Setting up\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS, KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\"../../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.2. Searching for KItems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section, we would like to search for specfic KItems we created in the DSMS.\n", + "\n", + "For this purpose, we will firstly create some KItems and apply the `search`-method on the `DSMS`-object later on in order to find them again in the DSMS.\n", + "\n", + "We also want to demonstrate here, that we can link KItems to each other in order to find e.g. a related item of type `DatasetCatalog`. For this strategy, we are using the `linked_kitems`- attribute and the `id` of the item which we would like to link.\n", + "\n", + "The procedure looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "item1 = KItem(\n", + " name=\"Machine-1\",\n", + " ktype_id=dsms.ktypes.TestingMachine,\n", + " custom_properties={\"Producer\": \"TestingLab GmBH\",\n", + " \"Room Number\": \"A404\",\n", + " \"Description\": \"Bending Test Machine\"\n", + " }\n", + ")\n", + "\n", + "item2 = KItem(\n", + " name=\"Machine-2\",\n", + " ktype_id=dsms.ktypes.TestingMachine,\n", + " custom_properties={\"Producer\": \"StressStrain GmBH\",\n", + " \"Room Number\": \"B500\",\n", + " \"Description\": \"Compression Test Machine\"\n", + " }\n", + ")\n", + "\n", + "item3 = KItem(\n", + " name=\"Specimen-1\", \n", + " ktype_id=dsms.ktypes.Specimen,\n", + " linked_kitems=[item1],\n", + " custom_properties={\"Geometry\": \"Cylindrical 150mm x 20mm x 40mm\",\n", + " \"Material\": \"Concrete\",\n", + " \"Project ID\": \"ConstructionProject2024\"\n", + " }\n", + "\n", + ")\n", + "item4 = KItem(\n", + " name=\"Specimen-2\",\n", + " ktype_id=dsms.ktypes.Specimen,\n", + " linked_kitems=[item2],\n", + " custom_properties={\"Geometry\": \"Rectangular 200mm x 30mm x 20mm\",\n", + " \"Material\": \"Metal\",\n", + " \"Project ID\": \"MetalBlenders2024\"\n", + " }\n", + ")\n", + "\n", + "item5 = KItem(\n", + " name=\"Research Institute ABC\",\n", + " ktype_id=dsms.ktypes.Organization,\n", + " linked_kitems=[item1],\n", + " annotations=[\n", + " {\n", + " \"iri\": \"www.researchBACiri.org/foo\",\n", + " \"name\": \"research ABC Institute\",\n", + " \"namespace\": \"research\",\n", + " }\n", + " ],\n", + ")\n", + "\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "####

    Note : Here in this tutorial, we use dsms.search with `limit=1` to maintain readability but the user can adjust the variable `limit` as per requirement.

    \n", + "\n", + "\n", + "Now, we are apply to search for e.g. kitems of type `TestingMachine`:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Machine-1, \n", + "\n", + "\tid = 0f49b0ad-2a98-4c10-8440-fd3c37363646, \n", + "\n", + "\tktype_id = testing-machine, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = machine-1-0f49b0ad, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [\n", + "\t\t\n", + "\t\t\tid: 075709da-5481-4cb4-a8ee-c18db34ce9d3\n", + "\t\t\tname: Specimen-1\n", + "\t\t\tslug: specimen-1-075709da\n", + "\t\t\tktype_id: specimen\n", + "\t\t\tsummary: None\n", + "\t\t\tavatar_exists: False\n", + "\t\t\tannotations: []\n", + "\t\t\tlinked_kitems: [{\n", + "\t\t\tid: 0f49b0ad-2a98-4c10-8440-fd3c37363646\n", + "\t\t}]\n", + "\t\t\texternal_links: []\n", + "\t\t\tcontacts: []\n", + "\t\t\tauthors: [{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}]\n", + "\t\t\tlinked_affiliations: []\n", + "\t\t\tattachments: []\n", + "\t\t\tuser_groups: []\n", + "\t\t\tcustom_properties: {'sampletype': None, 'sampleinformation': None, 'sampleproductionprocess': None, 'Geometry': 'Cylindrical 150mm x 20mm x 40mm', 'Material': 'Concrete', 'Project ID': 'ConstructionProject2024'}\n", + "\t\t\tcreated_at: 2024-08-19T18:26:00.499708\n", + "\t\t\tupdated_at: 2024-08-19T18:26:00.499708\n", + "\t\t\n", + "\t], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:26:00.247787, \n", + "\n", + "\tupdated_at = 2024-08-19 18:26:00.247787, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = {\n", + "\t\tProducer: TestingLab GmBH, \n", + "\t\tRoom Number: A404, \n", + "\t\tDescription: Bending Test Machine\n", + "\t}, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n", + "\n", + "\n" + ] + } + ], + "source": [ + "for result in dsms.search(ktypes=[dsms.ktypes.TestingMachine], limit=1):\n", + " print(result.hit)\n", + " print(\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` and `DatasetCatalog`:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Research Institute ABC, \n", + "\n", + "\tid = 21aa50c3-5ec2-4ac3-aba8-69071a4287e2, \n", + "\n", + "\tktype_id = organization, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = researchinstituteabc-21aa50c3, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:26:00.740761, \n", + "\n", + "\tupdated_at = 2024-08-19 18:26:00.740761, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = None, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n", + "fuzziness: False\n", + "\n", + "\n" + ] + } + ], + "source": [ + "for result in dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.DatasetCatalog], limit=1):\n", + " print(result.hit)\n", + " print(\"fuzziness: \", result.fuzzy)\n", + " print(\"\\n\")\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... or for all of type `Dataset` with `Specimen-1` in the name:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "for result in dsms.search(query=\"Specimen-1\", ktypes=[dsms.ktypes.Dataset], limit=1):\n", + " print(result.hit)\n", + " print(\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and for all of type `Organization` with the annotation `www.researchBACiri.org/foo`:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = Research Institute ABC, \n", + "\n", + "\tid = 84c089e6-feab-4ba6-a934-527594e504eb, \n", + "\n", + "\tktype_id = organization, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = researchinstituteabc-84c089e6, \n", + "\n", + "\tannotations = [\n", + "\t\t{\n", + "\t\t\tiri: www.researchBACiri.org/foo,\n", + "\t\t\tname: research ABC Institute,\n", + "\t\t\tnamespace: research,\n", + "\t\t\tdescription: None\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tattachments = [], \n", + "\n", + "\tlinked_kitems = [\n", + "\t\t\n", + "\t\t\tid: 694561e9-df15-4c71-ae71-abd91df3ec23\n", + "\t\t\tname: Machine-1\n", + "\t\t\tslug: machine-1-694561e9\n", + "\t\t\tktype_id: testing-machine\n", + "\t\t\tsummary: None\n", + "\t\t\tavatar_exists: False\n", + "\t\t\tannotations: []\n", + "\t\t\tlinked_kitems: [{\n", + "\t\t\tid: 83672cdb-7584-4dd7-9b6e-d10574f1adf7\n", + "\t\t}, {\n", + "\t\t\tid: 84c089e6-feab-4ba6-a934-527594e504eb\n", + "\t\t}]\n", + "\t\t\texternal_links: []\n", + "\t\t\tcontacts: []\n", + "\t\t\tauthors: [{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}]\n", + "\t\t\tlinked_affiliations: []\n", + "\t\t\tattachments: []\n", + "\t\t\tuser_groups: []\n", + "\t\t\tcustom_properties: {'Producer': 'TestingLab GmBH', 'Room Number': 'A404', 'Description': 'Bending Test Machine'}\n", + "\t\t\tcreated_at: 2024-08-19T18:26:08.898435\n", + "\t\t\tupdated_at: 2024-08-19T18:26:08.898435\n", + "\t\t\n", + "\t], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = False, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-19 18:26:09.390800, \n", + "\n", + "\tupdated_at = 2024-08-19 18:26:09.390800, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = None, \n", + "\n", + "\tdataframe = None, \n", + "\n", + "\trdf_exists = False\n", + ")\n", + "\n", + "\n" + ] + } + ], + "source": [ + "for result in dsms.search(\n", + " ktypes=[dsms.ktypes.Organization], annotations=[\"www.researchBACiri.org/foo\"], limit=1\n", + " ):\n", + " print(result.hit)\n", + " print(\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Clean up the DSMS from the tutortial:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item1]\n", + "del dsms[item2]\n", + "del dsms[item3]\n", + "del dsms[item4]\n", + "del dsms[item5]\n", + "\n", + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/6_apps.ipynb b/examples/tutorials/6_apps.ipynb new file mode 100644 index 0000000..48af684 --- /dev/null +++ b/examples/tutorials/6_apps.ipynb @@ -0,0 +1,1025 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6. DSMS Apps and Pipelines\n", + "\n", + "In this tutorial we see how to create apps and run them manually" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1: Setting up\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from dsms import DSMS, KItem, AppConfig\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now source the environmental variables from an `.env` file and start the DSMS-session." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "dsms = DSMS(env=\"../../.env\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.1. Investigating Available Apps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can investigate which apps are available:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[AppConfig(name=ckan-fetch, specification={'metadata': {'generateName': 'ckan-resource-request-'}}),\n", + " AppConfig(name=csv_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=csv_tensile_test_f2, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_notched_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_shear_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=excel_tensile_test, specification={'metadata': {'generateName': 'data2rdf-'}}),\n", + " AppConfig(name=ternary-plot, specification={'metadata': {'generateName': 'ckan-tenary-app-'}})]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.app_configs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2 Create a new app config and apply it to a KItem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2.1 Arbitrary python code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To be defined." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6.2.2 - Data2RDF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.1 Prepare app and its config" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following example, we would like to upload some csv with some arbitrary data and describe it through an RDF. This will give us the opportunity to harmonize the entities of the data file through ontological concepts and allow us to convert values of the data frame columns in to any compatible unit we would like to have.\n", + "\n", + "Fist of all, let us define the data:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"\"\"A,B,C\n", + "1.2,1.3,1.5\n", + "1.7,1.8,1.9\n", + "2.0,2.1,2.3\n", + "2.5,2.6,2.8\n", + "3.0,3.2,3.4\n", + "3.6,3.7,3.9\n", + "4.1,4.3,4.4\n", + "4.7,4.8,5.0\n", + "5.2,5.3,5.5\n", + "5.8,6.0,6.1\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also give the config a defined name:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "configname = \"testapp2\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a next step, we want to create a new app specification. The specification is following the definition of an [**Argo Workflow**](https://argo-workflows.readthedocs.io/en/latest/). The workflow shall trigger a pipeline from a docker image with the [**Data2RDF package**](https://data2rdf.readthedocs.io/en/latest/). \n", + "\n", + "The image has already been deployed on the k8s cluster of the DSMS and the workflow template with the name `dsms-data2rdf` has been implemented previously. Hence we only need to configure our pipeline for our data shown above, which we would like to upload and describe through an RDF.\n", + "\n", + "For more details about the data2rdf package, please refer to the documentation of Data2RDF mentioned above.\n", + "\n", + "The parameters of the app config are defining the inputs for our Data2RDF pipeline. This e.g. are: \n", + "\n", + "* the parser kind (`csv` here)\n", + "* the time series header length (`1` here)\n", + "* the metadata length (`0` here)\n", + "* the time series separator (`,` here)\n", + "* the log level (`DEBUG` here)\n", + "* the mapping \n", + " * `A` is the test time and has a unit in seconds\n", + " * `B` is the standard force in kilonewtons\n", + " * `C` is the absolut cross head travel in millimeters" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "parameters = [\n", + " {\"name\": \"parser\", \"value\": \"csv\"},\n", + " {\"name\": \"time_series_header_length\", \"value\": 1},\n", + " {\"name\": \"metadata_length\", \"value\": 0},\n", + " {\"name\": \"time_series_sep\", \"value\": \",\"},\n", + " {\"name\": \"log_level\", \"value\": \"DEBUG\"},\n", + " {\n", + " \"name\": \"mapping\",\n", + " \"value\": \"\"\"\n", + " [\n", + " {\n", + " \"key\": \"A\",\n", + " \"iri\": \"https://w3id.org/steel/ProcessOntology/TestTime\",\n", + " \"unit\": \"s\"\n", + " },\n", + " {\n", + " \"key\": \"B\",\n", + " \"iri\": \"https://w3id.org/steel/ProcessOntology/StandardForce\",\n", + " \"unit\": \"kN\"\n", + " },\n", + " {\n", + " \"key\": \"C\",\n", + " \"iri\": \"https://w3id.org/steel/ProcessOntology/AbsoluteCrossheadTravel\",\n", + " \"unit\": \"mm\"\n", + " }\n", + " ]\n", + " \"\"\",\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we add the parameters to our app specification. We assign a prefix `datardf-` which shall generate a new name with some random characters as suffix. The workflow template with the Docker image we want to run is called `dsms-data2rdf` and its `entrypoint` is `execute_pipeline`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Define app specification\n", + "specification = {\n", + " \"apiVersion\": \"argoproj.io/v1alpha1\",\n", + " \"kind\": \"Workflow\",\n", + " \"metadata\": {\"generateName\": \"data2rdf-\"},\n", + " \"spec\": {\n", + " \"entrypoint\": \"execute_pipeline\",\n", + " \"workflowTemplateRef\": {\"name\": \"dsms-data2rdf\"},\n", + " \"arguments\": {\"parameters\": parameters},\n", + " },\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we instanciate the new app config:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "appspec = AppConfig(\n", + " name=configname,\n", + " specification=specification, # this can also be a file path to a yaml file instead of a dict\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We commit the new app config:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we would like to apply the app config to a KItem. The set the `triggerUponUpload` must be set to `True` so that the app is triggered automatically when we upload an attachment.\n", + "\n", + "Additionally, we must tell the file extension for which the upload shall be triggered. Here it is `.csv`.\n", + "\n", + "We also want to generate a qr code as avatar for the KItem with `avatar={\"include_qr\": True}`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "item = KItem(\n", + " name=\"my tensile test experiment\",\n", + " ktype_id=dsms.ktypes.Dataset,\n", + " kitem_apps=[\n", + " {\n", + " \"executable\": appspec.name,\n", + " \"title\": \"data2rdf\",\n", + " \"additional_properties\": {\n", + " \"triggerUponUpload\": True,\n", + " \"triggerUponUploadFileExtensions\": [\".csv\"],\n", + " },\n", + " }\n", + " ],\n", + " avatar={\"include_qr\": True},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We commit the KItem:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we add our data with our attachment:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "item.attachments = [{\"name\": \"dummy_data.csv\", \"content\": data}]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And we commit again:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.2 Get results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can verify that the data extraction was successful:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KItem(\n", + "\n", + "\tname = my tensile test experiment, \n", + "\n", + "\tid = 58683a2d-7823-4638-bc8e-91b461afa593, \n", + "\n", + "\tktype_id = dataset, \n", + "\n", + "\tin_backend = True, \n", + "\n", + "\tslug = mytensiletestexperiment-58683a2d, \n", + "\n", + "\tannotations = [], \n", + "\n", + "\tattachments = [\n", + "\t\t{\n", + "\t\t\tname: dummy_data.csv\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tlinked_kitems = [], \n", + "\n", + "\taffiliations = [], \n", + "\n", + "\tauthors = [\n", + "\t\t{\n", + "\t\t\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tavatar_exists = True, \n", + "\n", + "\tcontacts = [], \n", + "\n", + "\tcreated_at = 2024-08-20 08:04:25.756982, \n", + "\n", + "\tupdated_at = 2024-08-20 08:04:25.756982, \n", + "\n", + "\texternal_links = [], \n", + "\n", + "\tkitem_apps = [\n", + "\t\t{\n", + "\t\t\tkitem_app_id: 22,\n", + "\t\t\texecutable: testapp2,\n", + "\t\t\ttitle: data2rdf,\n", + "\t\t\tdescription: None,\n", + "\t\t\ttags: None,\n", + "\t\t\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv']}\n", + "\t\t}\n", + "\t], \n", + "\n", + "\tsummary = None, \n", + "\n", + "\tuser_groups = [], \n", + "\n", + "\tcustom_properties = None, \n", + "\n", + "\tdataframe = [\n", + "\t\t{\n", + "\t\t\tcolumn_id: 0,\n", + "\t\t\tname: TestTime\n", + "\t\t}, \n", + "\t\t{\n", + "\t\t\tcolumn_id: 1,\n", + "\t\t\tname: StandardForce\n", + "\t\t}, \n", + "\t\t{\n", + "\t\t\tcolumn_id: 2,\n", + "\t\t\tname: AbsoluteCrossheadTravel\n", + "\t\t}\n", + "\t], \n", + "\n", + "\trdf_exists = True\n", + ")\n" + ] + } + ], + "source": [ + "print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And also that the RDF generation was successful:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "@prefix csvw: .\n", + "@prefix dcat: .\n", + "@prefix dcterms: .\n", + "@prefix ns1: .\n", + "@prefix ns2: .\n", + "@prefix rdfs: .\n", + "@prefix xsd: .\n", + "\n", + " a dcat:Dataset ;\n", + " dcterms:hasPart ;\n", + " dcat:distribution [ a dcat:Distribution ;\n", + " dcat:accessURL \"https://bue.materials-data.space/api/knowledge/data_api/58683a2d-7823-4638-bc8e-91b461afa593\"^^xsd:anyURI ;\n", + " dcat:mediaType \"http://www.iana.org/assignments/media-types/text/csv\"^^xsd:anyURI ] .\n", + "\n", + " a ;\n", + " ns1:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI .\n", + "\n", + " a ;\n", + " ns1:hasUnit \"http://qudt.org/vocab/unit/KiloN\"^^xsd:anyURI .\n", + "\n", + " a ;\n", + " ns1:hasUnit \"http://qudt.org/vocab/unit/SEC\"^^xsd:anyURI .\n", + "\n", + " a csvw:TableGroup ;\n", + " csvw:table [ a csvw:Table ;\n", + " rdfs:label \"Time series data\" ;\n", + " csvw:tableSchema [ a csvw:Schema ;\n", + " csvw:column [ a csvw:Column ;\n", + " ns1:quantity ;\n", + " csvw:titles \"C\"^^xsd:string ;\n", + " ns2:page [ a ns2:Document ;\n", + " dcterms:format \"https://www.iana.org/assignments/media-types/application/json\"^^xsd:anyURI ;\n", + " dcterms:identifier \"https://bue.materials-data.space/api/knowledge/data_api/column-2\"^^xsd:anyURI ;\n", + " dcterms:type \"http://purl.org/dc/terms/Dataset\"^^xsd:anyURI ] ],\n", + " [ a csvw:Column ;\n", + " ns1:quantity ;\n", + " csvw:titles \"B\"^^xsd:string ;\n", + " ns2:page [ a ns2:Document ;\n", + " dcterms:format \"https://www.iana.org/assignments/media-types/application/json\"^^xsd:anyURI ;\n", + " dcterms:identifier \"https://bue.materials-data.space/api/knowledge/data_api/column-1\"^^xsd:anyURI ;\n", + " dcterms:type \"http://purl.org/dc/terms/Dataset\"^^xsd:anyURI ] ],\n", + " [ a csvw:Column ;\n", + " ns1:quantity ;\n", + " csvw:titles \"A\"^^xsd:string ;\n", + " ns2:page [ a ns2:Document ;\n", + " dcterms:format \"https://www.iana.org/assignments/media-types/application/json\"^^xsd:anyURI ;\n", + " dcterms:identifier \"https://bue.materials-data.space/api/knowledge/data_api/column-0\"^^xsd:anyURI ;\n", + " dcterms:type \"http://purl.org/dc/terms/Dataset\"^^xsd:anyURI ] ] ] ] .\n", + "\n", + "\n" + ] + } + ], + "source": [ + "print(item.subgraph.serialize())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we are able to convert our data into any compatiable unit we want. For the `StandardForce`, it was previously `kN`, but we want to have it in `N` now:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1300.0,\n", + " 1800.0,\n", + " 2100.0,\n", + " 2600.0,\n", + " 3200.0,\n", + " 3700.0,\n", + " 4300.0,\n", + " 4800.0,\n", + " 5300.0,\n", + " 6000.0]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item.dataframe.StandardForce.convert_to(\"N\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.3 Manipulate dataframe" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to retrieve the dataframe as pd.DataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    TestTimeStandardForceAbsoluteCrossheadTravel
    01.21.31.5
    11.71.81.9
    22.02.12.3
    32.52.62.8
    43.03.23.4
    53.63.73.9
    64.14.34.4
    74.74.85.0
    85.25.35.5
    95.86.06.1
    \n", + "
    " + ], + "text/plain": [ + " TestTime StandardForce AbsoluteCrossheadTravel\n", + "0 1.2 1.3 1.5\n", + "1 1.7 1.8 1.9\n", + "2 2.0 2.1 2.3\n", + "3 2.5 2.6 2.8\n", + "4 3.0 3.2 3.4\n", + "5 3.6 3.7 3.9\n", + "6 4.1 4.3 4.4\n", + "7 4.7 4.8 5.0\n", + "8 5.2 5.3 5.5\n", + "9 5.8 6.0 6.1" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item.dataframe.to_df()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to overwrite the dataframe with new data:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "item.dataframe = {\n", + " \"TestTime\": list(range(100)),\n", + " \"StandardForce\": list(range(1,101)),\n", + " \"AbsoluteCrossheadTravel\": list(range(2,102))\n", + "}\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to retrieve the data colum-wise:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "column: TestTime ,\n", + " data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]\n", + "column: StandardForce ,\n", + " data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]\n", + "column: AbsoluteCrossheadTravel ,\n", + " data: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101]\n" + ] + } + ], + "source": [ + "for column in item.dataframe:\n", + " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and also to modify the dataframe directly as we need:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "new_df = item.dataframe.to_df().drop(['TestTime'], axis=1)\n", + "item.dataframe = new_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6.2.2.4 Run app on demand" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to run the app on demand, not being triggered automatically during the upload of an attachment every time. For this purpose, we just need to refer to the name of the app we assigned during the KItem creation ( here it is simply `data2rdf`).\n", + "\n", + "Additionally, we need to tell the `attachment_name` and hand over the access token and host url to the app by explicitly setting `set_token` and `set_host_url` to `True`.\n", + "\n", + "The app is running synchronously, hence the `job` is created when the pipeline run finished." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "job = item.kitem_apps.by_title[\"data2rdf\"].run(\n", + " attachment_name=item.attachments[0].name,\n", + " set_token=True,\n", + " set_host_url=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to retrieve the job status:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "JobStatus(phase='Succeeded', estimated_duration=None, finished_at='08/20/2024, 08:05:35', started_at='08/20/2024, 08:05:15', message=None, progress='1/1')" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job.status" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and the job logs:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\"[2024-08-20 08:05:23,481 - dsms_data2rdf.main - INFO]: Fetch KItem: \\n KItem(\\n\\n\\tname = my tensile test experiment, \\n\\n\\tid = 58683a2d-7823-4638-bc8e-91b461afa593, \\n\\n\\tktype_id = dataset, \\n\\n\\tin_backend = True, \\n\\n\\tslug = mytensiletestexperiment-58683a2d, \\n\\n\\tannotations = [], \\n\\n\\tattachments = [\\n\\t\\t{\\n\\t\\t\\tname: dummy_data.csv\\n\\t\\t}\\n\\t], \\n\\n\\tlinked_kitems = [], \\n\\n\\taffiliations = [], \\n\\n\\tauthors = [\\n\\t\\t{\\n\\t\\t\\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\\n\\t\\t}\\n\\t], \\n\\n\\tavatar_exists = True, \\n\\n\\tcontacts = [], \\n\\n\\tcreated_at = 2024-08-20 08:04:25.756982, \\n\\n\\tupdated_at = 2024-08-20 08:04:25.756982, \\n\\n\\texternal_links = [], \\n\\n\\tkitem_apps = [\\n\\t\\t{\\n\\t\\t\\tkitem_app_id: 22,\\n\\t\\t\\texecutable: testapp2,\\n\\t\\t\\ttitle: data2rdf,\\n\\t\\t\\tdescription: None,\\n\\t\\t\\ttags: None,\\n\\t\\t\\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv']}\\n\\t\\t}\\n\\t], \\n\\n\\tsummary = None, \\n\\n\\tuser_groups = [], \\n\\n\\tcustom_properties = None, \\n\\n\\tdataframe = [\\n\\t\\t{\\n\\t\\t\\tcolumn_id: 0,\\n\\t\\t\\tname: TestTime\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 1,\\n\\t\\t\\tname: StandardForce\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 2,\\n\\t\\t\\tname: AbsoluteCrossheadTravel\\n\\t\\t}\\n\\t], \\n\\n\\trdf_exists = True\\n)\\n[2024-08-20 08:05:23,494 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser arguments: {'metadata_sep': ',', 'metadata_length': '0', 'time_series_sep': ',', 'time_series_header_length': '1', 'drop_na': 'false', 'fillna': ''}\\n[2024-08-20 08:05:23,494 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser: Parser.csv\\n[2024-08-20 08:05:23,494 - dsms_data2rdf.main - INFO]: Run pipeline with the following config: {'base_iri': 'https://bue.materials-data.space/58683a2d-7823-4638-bc8e-91b461afa593', 'data_download_uri': 'https://bue.materials-data.space/api/knowledge/data_api/58683a2d-7823-4638-bc8e-91b461afa593', 'graph_identifier': 'https://bue.materials-data.space/58683a2d-7823-4638-bc8e-91b461afa593', 'separator': '/', 'encoding': 'utf-8'}\\n[2024-08-20 08:05:28,419 - dsms_data2rdf.main - INFO]: Pipeline did detect any metadata. Will not make annotations for KItem\\n[2024-08-20 08:05:28,810 - dsms_data2rdf.main - INFO]: Checking that dataframe is up to date.\\n[2024-08-20 08:05:28,810 - dsms_data2rdf.main - INFO]: Dataframe upload was successful after 0 retries.\\n[2024-08-20 08:05:28,810 - dsms_data2rdf.main - INFO]: Done!\\n\"\n" + ] + } + ], + "source": [ + "print(job.logs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In case we would like to run the job in the background, we simply add a `wait=False`:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "job = item.kitem_apps.by_title[\"data2rdf\"].run(\n", + " attachment_name=item.attachments[0].name,\n", + " set_token=True,\n", + " set_host_url=True,\n", + " wait=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are able to monitor the job status and logs asynchronously:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Running' estimated_duration=None finished_at=None started_at='08/20/2024, 08:05:39' message=None progress='0/1'\n", + "\n", + " Current logs:\n", + "\"\"\n", + "\n", + " Current status:\n", + "phase='Succeeded' estimated_duration=None finished_at='08/20/2024, 08:05:59' started_at='08/20/2024, 08:05:39' message=None progress='1/1'\n", + "\n", + " Current logs:\n", + "\"[2024-08-20 08:05:45,472 - dsms_data2rdf.main - INFO]: Fetch KItem: \\n KItem(\\n\\n\\tname = my tensile test experiment, \\n\\n\\tid = 58683a2d-7823-4638-bc8e-91b461afa593, \\n\\n\\tktype_id = dataset, \\n\\n\\tin_backend = True, \\n\\n\\tslug = mytensiletestexperiment-58683a2d, \\n\\n\\tannotations = [], \\n\\n\\tattachments = [\\n\\t\\t{\\n\\t\\t\\tname: dummy_data.csv\\n\\t\\t}\\n\\t], \\n\\n\\tlinked_kitems = [], \\n\\n\\taffiliations = [], \\n\\n\\tauthors = [\\n\\t\\t{\\n\\t\\t\\tuser_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\\n\\t\\t}\\n\\t], \\n\\n\\tavatar_exists = True, \\n\\n\\tcontacts = [], \\n\\n\\tcreated_at = 2024-08-20 08:04:25.756982, \\n\\n\\tupdated_at = 2024-08-20 08:04:25.756982, \\n\\n\\texternal_links = [], \\n\\n\\tkitem_apps = [\\n\\t\\t{\\n\\t\\t\\tkitem_app_id: 22,\\n\\t\\t\\texecutable: testapp2,\\n\\t\\t\\ttitle: data2rdf,\\n\\t\\t\\tdescription: None,\\n\\t\\t\\ttags: None,\\n\\t\\t\\tadditional_properties: {triggerUponUpload: True, triggerUponUploadFileExtensions: ['.csv']}\\n\\t\\t}\\n\\t], \\n\\n\\tsummary = None, \\n\\n\\tuser_groups = [], \\n\\n\\tcustom_properties = None, \\n\\n\\tdataframe = [\\n\\t\\t{\\n\\t\\t\\tcolumn_id: 0,\\n\\t\\t\\tname: TestTime\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 1,\\n\\t\\t\\tname: StandardForce\\n\\t\\t}, \\n\\t\\t{\\n\\t\\t\\tcolumn_id: 2,\\n\\t\\t\\tname: AbsoluteCrossheadTravel\\n\\t\\t}\\n\\t], \\n\\n\\trdf_exists = True\\n)\\n[2024-08-20 08:05:45,485 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser arguments: {'metadata_sep': ',', 'metadata_length': '0', 'time_series_sep': ',', 'time_series_header_length': '1', 'drop_na': 'false', 'fillna': ''}\\n[2024-08-20 08:05:45,485 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser: Parser.csv\\n[2024-08-20 08:05:45,485 - dsms_data2rdf.main - INFO]: Run pipeline with the following config: {'base_iri': 'https://bue.materials-data.space/58683a2d-7823-4638-bc8e-91b461afa593', 'data_download_uri': 'https://bue.materials-data.space/api/knowledge/data_api/58683a2d-7823-4638-bc8e-91b461afa593', 'graph_identifier': 'https://bue.materials-data.space/58683a2d-7823-4638-bc8e-91b461afa593', 'separator': '/', 'encoding': 'utf-8'}\\n[2024-08-20 08:05:50,422 - dsms_data2rdf.main - INFO]: Pipeline did detect any metadata. Will not make annotations for KItem\\n[2024-08-20 08:05:50,816 - dsms_data2rdf.main - INFO]: Checking that dataframe is up to date.\\n[2024-08-20 08:05:50,816 - dsms_data2rdf.main - INFO]: Dataframe upload was successful after 0 retries.\\n[2024-08-20 08:05:50,816 - dsms_data2rdf.main - INFO]: Done!\\n\"\n" + ] + } + ], + "source": [ + "while True:\n", + " time.sleep(1)\n", + " print(\"\\n Current status:\")\n", + " print(job.status)\n", + " print(\"\\n Current logs:\")\n", + " print(job.logs)\n", + " if job.status.phase != \"Running\":\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**IMPORTANT**: When job has run asychronously (in the background), we need to manually refresh the KItem afterwards:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "item.refresh()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Clean up the DSMS from the tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[item]\n", + "del dsms[appspec]\n", + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sdk", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/testfile.txt b/examples/tutorials/testfile.txt new file mode 100644 index 0000000..7976166 --- /dev/null +++ b/examples/tutorials/testfile.txt @@ -0,0 +1 @@ +This is a calibration protocol!