diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4cfb56..eba799f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6316dfb..d076a78 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: check-json - id: check-yaml @@ -14,25 +14,25 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 26.5.1 hooks: - id: black args: [--line-length, "79"] - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 8.0.1 hooks: - id: isort - args: [--profile, black, --filter-files] + args: [--profile, black, --filter-files, --line-length, "79"] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.21.2 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 7.3.0 hooks: - id: flake8 args: [--count, --show-source, --statistics, '--ignore', 'E501,E203,W503,E201,E202,E221,E222,E231,E241,E271,E272,E702,E713'] @@ -40,27 +40,24 @@ repos: log_file: flake8.log - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 + rev: v3.2.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/PyCQA/bandit - rev: 1.7.5 + rev: 1.9.4 hooks: - id: bandit args: ["-r"] files: ^(dsms)/.* - additional_dependencies: - - "pbr==2.0.0" - - setuptools - repo: local hooks: - id: pylint name: pylint entry: pylint - args: ["--rcfile=.pylintrc", "--extension-pkg-whitelist='pydantic'"] - language: python + args: ["--rcfile=.pylintrc", "--extension-pkg-whitelist=pydantic"] + language: system types: [python] require_serial: true files: ^(dsms)/.* diff --git a/.pylintrc b/.pylintrc index a17714e..36163aa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -72,6 +72,7 @@ disable= no-name-in-module, cyclic-import, import-outside-toplevel, too-many-arguments, + too-many-positional-arguments, too-many-nested-blocks, dangerous-default-value, too-many-public-methods, @@ -80,6 +81,7 @@ disable= no-name-in-module, too-many-branches, too-many-lines, too-many-statements, + global-statement, @@ -245,7 +247,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis). It # supports qualified module names, as well as Unix pattern matching. -ignored-modules= +ignored-modules=dotenv,pydantic,pydantic_settings,yaml,oyaml,requests,click,pandas,rdflib,PIL,segno # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5c856c7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,114 @@ +# Changelog + +All notable changes to this project are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [5.0.0] — unreleased + +### Added + +**User model** +- `User` gains optional `firstName`, `lastName`, and `email` fields populated from the Keycloak user profile. + +**Access control (RBAC)** +- `KItemAccessProperties` model with `user_access` and `group_access` lists for per-KItem role assignments. +- `Role` enum (`MEMBER=1`, `CONTRIBUTOR=2`, `OWNER=3`) and `OperationType` enum (`create`, `read`, `update`, `delete`, `manage`). Roles serialize as lowercase strings on the wire (`"member"`, `"contributor"`, `"owner"`). +- `RoleMapping` enum with `get_operations`, `min_access_level`, and `max_access_level` helpers. +- `UserAccessProperty` and `GroupAccessProperty` sub-models. +- `DSMS.user_groups` and `DSMS.users` cached properties with `refresh_user_groups()` / `refresh_users()` invalidation. +- `DSMS.get_user(user_id)` convenience method. +- `Group`, `User`, `GroupList`, `UserList` models (`dsms.knowledge.groups`). +- `INTERNAL_GROUP` / `PUBLIC_GROUP` constants (IDs `dsms:internal` / `dsms:public`), configurable via environment variables. +- `refresh_public_groups(config)` to avoid import-time staleness when custom group IDs are used. + +**KType v2 semantic-spec subsystem** +- `KTypeSpec` model capturing the full `ktype_spec` database record: ontology classes, relations, semantic schema references, inheritance, tags, versioning, stash state. +- Sub-models: `OntologyClassSpec`, `SemanticSchemaRef`, `RelationSpec`. +- `KTypeV2(KType)` response model with optional `spec: KTypeSpec` field. +- Request models: `CreateKTypeRequest`, `ImportFromUrlRequest`, `KTypeSpecPayload`. +- Remote-repository models: `RemoteKTypeSummary`, `RemoteKTypeVersion`, `RemoteSchemaInfo`, `RemoteSchemaVersionInfo`, `SpecDiffField`, `RemoteDiffOut`. +- Full v2 CRUD surface on `DSMS`: `get_v2_ktypes`, `get_v2_ktype`, `create_v2_ktype`, `import_v2_ktype`, `update_v2_ktype`, `delete_v2_ktype`, `restore_v2_ktype_stash`, `refresh_v2_ktype`, `export_v2_ktype`, `list_remote_v2_ktypes`, `list_remote_schemas`, `list_remote_ktype_versions`, `get_v2_ktype_remote_diff`. +- `DSMS.get_ktypes_by_parent(parent_id)` for the `?parent=` filter on the v1 list endpoint. + +**Schema data on KItems** +- `KItemSchemaData` model (`schema_id`, `content`) and `KItemSchemaDataList` helper with `.by_schema_id` lookup. +- `KItem.schema_data: Optional[List[KItemSchemaData]]` field. +- Commit flow syncs `schema_data` changes via `PUT`/`DELETE` on `/api/knowledge/{kitem_id}/schema-data/{schema_id}`. + +**Context SPARQL** +- `SparqlInterface.query_context(context_id, query)` — `POST /api/knowledge/sparql/context`. +- `SparqlInterface.graph_context(context_id, query)` — `POST /api/knowledge/graph/context`. + +**Search and list additions** +- `DSMS.search()` gains `contexts: List[str]` (filter by context KItem IDs) and `attachment_extensions: List[str]` (filter by file extension). +- `DSMS.get_kitems()` gains `name: str` for substring filtering. + +**KItemCompactedModel additions** +- `has_contexts: bool` — whether the KItem belongs to at least one context. +- `attachment_extensions: Optional[List[str]]` — unique file extensions in the KItem's attachments. +- `avatar_exists` moved from `KItem` to the shared `KItemCompactedModel` base. + +### Changed + +- `KItem.contexts` field now properly tracks changes in `_get_kitems_diffs()`. +- `KItem.access_properties` changes are tracked and committed. +- `get_user_by_id` now accepts `dsms` as its first argument (consistent with all util functions) and returns a typed `User` object. +- `Role.min_access_level` / `max_access_level` now return `Role` objects and raise `ValueError` for operations not granted by any role (e.g. `CREATE`). + +### Deprecated + +- `KItem.authors` — the server no longer populates this field. Use `access_properties` instead. +- `KItem.rdf_exists` — the server no longer populates this field. +- `KItem.user_groups` — legacy field still supported but superseded by `access_properties`. + +### Maintenance + +- Upgraded pre-commit hooks: `pre-commit-hooks` v4→v6, `black` 23→26, `isort` 5→8, `setup-cfg-fmt` v2→v3, `bandit` 1.9.3→1.9.4. +- Aligned `isort` line length to match `black`'s 79-character limit. +- Relaxed dependency pins: `rdflib>=6,<8`, `pandas>=2,<4`, `segno>=1.6,<2`, `pydantic-settings>=2,<3`, `oyaml>=1`. +- Dropped Python 3.8 and 3.9 support. + +--- + +## [4.0.0] + +### Added + +- Pydantic v2 migration (`BaseModel`, `field_validator`, `field_serializer`, `model_dump`). +- Service-account authentication via `client_id` / `client_secret` (Keycloak). +- `KItemAccessProperties` groundwork, `UserGroup` model. +- `AppConfig` and `DSMS.apps` for managing application configurations. +- `ProcessSchema` and `WebformSchema` models on `KType`. + +### Changed + +- Minimum Python version raised to 3.10. +- `KType.id` accepts both `UUID` and `str`. +- `Configuration` uses `pydantic-settings` for environment-variable loading. + +--- + +## [3.x] + +- Triplestore / SPARQL interface (`SparqlInterface`, subgraph CRUD). +- `Attachment`, `Avatar`, `ExternalLink`, `LinkedKItems` property models. +- DataFrame integration via `dataframe` field on `KItem`. +- `DSMS.search()` initial implementation. + +--- + +## [2.x] + +- Initial Pydantic v1 models for `KItem` and `KType`. +- Basic CRUD operations via `DSMS.add()`, `DSMS.delete()`, `DSMS.commit()`. +- Annotation support. + +--- + +## [< 2.0.0] + +- Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4b509ea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,151 @@ +# Contributing to DSMS Python SDK + +Thank you for considering a contribution! This document explains how to set up a development environment, run the checks we require, and submit changes. + +--- + +## Table of Contents + +1. [Development setup](#development-setup) +2. [Code style and linting](#code-style-and-linting) +3. [Pre-commit hooks](#pre-commit-hooks) +4. [Testing](#testing) +5. [Branching and commits](#branching-and-commits) +6. [Opening a pull request](#opening-a-pull-request) +7. [Versioning](#versioning) +8. [Reporting issues](#reporting-issues) + +--- + +## Development setup + +```bash +git clone git@github.com:MI-FraunhoferIWM/dsms-python-sdk.git +cd dsms-python-sdk +python -m venv .venv +source .venv/bin/activate +pip install -e ".[dev]" +``` + +The `[dev]` extra installs all development dependencies including linters, test runners, and the pre-commit framework. + +--- + +## Code style and linting + +We enforce a consistent style automatically via pre-commit hooks (see below). The key rules are: + +| Tool | Configuration | +|:---------:|:---------------------------------------------:| +| `black` | Line length 79, enforced on all `.py` files | +| `isort` | Profile `black`, line length 79 | +| `flake8` | Default rules, line length inferred from black | +| `pylint` | `fail-under=10.0`; see `.pylintrc` for disabled checks | +| `bandit` | Security linting | +| `pyupgrade` | Enforces modern Python syntax | + +Do **not** bypass hooks with `--no-verify`. If a hook fails, fix the underlying issue. + +--- + +## Pre-commit hooks + +Install the hooks once after cloning: + +```bash +pip install pre-commit +pre-commit install +``` + +Run manually against all changed files: + +```bash +pre-commit run --files ... +``` + +Run against all files in the repo: + +```bash +pre-commit run --all-files +``` + +To update hook versions to the latest stable releases: + +```bash +pre-commit autoupdate +``` + +--- + +## Testing + +Run the unit test suite with: + +```bash +pytest +``` + +Tests live under `tests/`. We do not mock the database in integration tests — if you add a test that touches the backend, it must run against a real DSMS instance configured via environment variables (see `Configuration` in `dsms/core/configuration.py`). + +**Tutorial notebooks** can be tested against a live instance with: + +```bash +./scripts/run_notebooks.sh +``` + +To re-execute notebooks and save outputs in-place (for documentation commits): + +```bash +./scripts/run_notebooks.sh --refresh +``` + +See `scripts/run_notebooks.sh --help` (or read the script header) for full usage. Requires `pip install -e ".[docs,tests]"` and a reachable DSMS instance. + +--- + +## Branching and commits + +- Base feature branches off `main`. +- Use descriptive branch names, e.g. `feature/ktype-v2-subsystem` or `fix/search-context-filter`. +- Keep commits focused. One logical change per commit. +- Write commit messages in the imperative mood: *"Add schema_data field to KItem"*, not *"Added"* or *"Adding"*. +- Do not amend published commits. + +--- + +## Opening a pull request + +1. Push your branch and open a PR against `main`. +2. Fill in the PR template — at minimum, describe **what** changed and **why**. +3. Ensure all CI checks pass before requesting a review. +4. Add an entry to `CHANGELOG.md` under the relevant unreleased section. +5. Update any affected documentation in `docs/`. + +PRs that introduce new public API surface should update: +- `dsms/knowledge/__init__.py` — re-export new models. +- `docs/dsms_sdk/dsms_kitem_schema.md` or `docs/dsms_sdk/dsms_sdk.md` — document new fields/methods. + +--- + +## Versioning + +This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html): + +- **MAJOR** — breaking API changes. +- **MINOR** — backward-compatible new functionality. +- **PATCH** — backward-compatible bug fixes. + +The version is set in `setup.cfg` (`version = vMAJOR.MINOR.PATCH`). Update it and `CHANGELOG.md` together as part of a release PR. + +The SDK version must stay compatible with the target DSMS backend version. See the compatibility table in `README.md`. + +--- + +## Reporting issues + +Please open an issue at and include: + +- SDK version (`pip show dsms-sdk`). +- Python version. +- A minimal reproducible example. +- The full traceback if applicable. diff --git a/README.md b/README.md index dc34313..0951366 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,18 @@ pip install -e . 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 +- Managing Knowledge Items (KItems), which are data instances of an explicitly defined semantic class type (KType) + - Creating, updating and deleting metadata and properties, e.g. date, operator, material response data for a conducted tensile test + - Contact information and supplementary information upon making changes or adding KItems + - Semantic annotation of KItems + - Attaching semantic schema data (ontology-class instance data) to KItems +- Managing Knowledge Types (KTypes), including the v2 semantic-spec subsystem for defining ontology classes, relations, and schema references +- Role-based access control (RBAC) per KItem: assign users and groups to `MEMBER`, `CONTRIBUTOR`, or `OWNER` roles +- Conduct free-text searches within the DSMS instance with filters (KType, annotation, context membership, attachment extension) as well as a full SPARQL interface (including context-scoped queries) +- Linking KItems to other KItems, and grouping them via context KItems +- Linking Apps to KItems, triggered, 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) +- Export of a knowledge (sub)graph as common serializations (.ttl, .json) ## Documentation @@ -49,7 +52,8 @@ Please take the compability of the SDK version with the DSMS version into accoun | >=3.0.4, <3.1.0 | >=3.0.5, <3.1.0 | | >=3.1.0, <3.2.2 | >=3.1.0, <3.2.1 | | >=3.2.2 | >=3.2.1, <4.0.0 | -| >=4.0.0 | >=4.0.0 | +| >=4.0.0, <5.0.0 | >=4.0.0, <5.0.0 | +| >=5.0.0 | >=5.0.0 | ## Tutorials @@ -57,7 +61,7 @@ Please take the compability of the SDK version with the DSMS version into accoun Please have a look at our tutorials on _readthedocs_: * [1. Introduction](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/1_introduction.html) * [2. Creation](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/2_creation.html) -* [3. Updation](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/3_updation.html) +* [3. Updating](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/3_updating.html) * [4. Deletion](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/4_deletion.html) * [5. Search](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/5_search.html) * [6. Apps](https://dsms-python-sdk.readthedocs.io/en/latest/dsms_sdk/tutorials/6_apps.html) @@ -65,23 +69,15 @@ Please have a look at our tutorials on _readthedocs_: Or try our Jupyter Notebooks: * [1. Introduction](docs/dsms_sdk/tutorials/1_introduction.ipynb) * [2. Creation](docs/dsms_sdk/tutorials/2_creation.ipynb) -* [3. Updation](docs/dsms_sdk/tutorials/3_updation.ipynb) +* [3. Updating](docs/dsms_sdk/tutorials/3_updating.ipynb) * [4. Deletion](docs/dsms_sdk/tutorials/4_deletion.ipynb) * [5. Search](docs/dsms_sdk/tutorials/5_search.ipynb) * [6. Apps](docs/dsms_sdk/tutorials/6_apps.ipynb) * [7. KTypes](docs/dsms_sdk/tutorials/7_ktypes.ipynb) -## Authors +## Contributing -[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) - -[Priyabrat Mishra](mailto:priyabrat.mishra@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) - -[Arjun Gopalakrishnan](mailto:arjun.gopalakrishnan@iwm.fraunhofer.de) (Fraunhofer Institute for Mechanics of Materials IWM) +See [CONTRIBUTING.md](CONTRIBUTING.md). All contributors are listed on the [GitHub contributors page](https://github.com/MI-FraunhoferIWM/dsms-python-sdk/graphs/contributors). ## License @@ -90,6 +86,6 @@ This project is licensed under the BSD 3-Clause. See the LICENSE file for more i ## Disclaimer -Copyright (c) 2014-2024, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. acting on behalf of its Fraunhofer IWM. +Copyright (c) 2014-2026, 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/dsms_sdk/dsms_kitem_schema.md b/docs/dsms_sdk/dsms_kitem_schema.md index 4ad3854..7acf6c4 100644 --- a/docs/dsms_sdk/dsms_kitem_schema.md +++ b/docs/dsms_sdk/dsms_kitem_schema.md @@ -9,6 +9,8 @@ The schema contains complex types and references, indicating an advanced usage s ![kitem_schema_uml](../assets/images/UML_KItem_schema.jpg) +`KItem` inherits the compacted base fields listed in [KItemCompactedModel fields](#kitemcompactedmodel-fields) below, and adds the following full-detail fields: + | Field Name | Description | Type | Default | Property Namespace | Required / Optional | |:-----------------:|:--------------------------------------------------------------------------------------------------------:|:-------------------------------------------------:|:--------:|:------------------:|:-----------------:| | Name | Human-readable name of the KItem. | string | Not Applicable | `name` | Required | @@ -18,7 +20,6 @@ The schema contains complex types and references, indicating an advanced usage s | 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 | | [KItemCustomPropertiesModel](#kitemcustompropertiesmodel) | 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 | | Apps | A list of applications associated with the KItem | List[[App](#app-fields)] | `[ ]` | `apps` | Optional | @@ -26,14 +27,32 @@ The schema contains complex types and references, indicating an advanced usage s | 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"]] | `[ ]` | `linked_kitems` | Optional | -| User Groups | User groups with access to this KItem | List[[UserGroup](#usergroup-fields)] | `[ ]` | `user_groups` | Optional | +| Contexts | Context KItems this KItem belongs to | List[Union[KItem, KItemCompactedModel]] | `[ ]` | `contexts` | Optional | +| Access Properties | Role-based access control entries for users and groups | [KItemAccessProperties](#kitemaccesrproperties-fields) | `None` | `access_properties`| Optional | +| Schema Data | Semantic schema data entries (ontology-class instance data) associated with this KItem | List[[KItemSchemaData](#kitemschemeadata-fields)] | `None` | `schema_data` | Optional | +| User Groups | *(Legacy)* User groups with access to this KItem. Prefer `access_properties` for new code. | List[[UserGroup](#usergroup-fields)] | `[ ]` | `user_groups` | Optional | +| Authors | *(Deprecated)* Authorship list. No longer populated by the server; use `access_properties` instead. | List[Author] | `[ ]` | `authors` | Deprecated | +| RDF Exists | *(Deprecated)* Whether the KItem holds an RDF graph. No longer populated by the server. | boolean | `False` | `rdf_exists` | Deprecated | + +## KItemCompactedModel Fields + +`KItemCompactedModel` is the lightweight representation returned by the search endpoint. `KItem` extends it with all the full-detail fields listed above. + +| Field Name | Description | Type | Default | Property Namespace | Required / Optional | +|:----------------------:|:------------------------------------------------------------------------:|:---------------------:|:-------:|:------------------------:|:----------------------------:| +| Name | Human-readable name of the KItem. | string | — | `name` | Required | +| ID | ID of the KItem. | UUID | auto | `id` | Optional | +| Ktype ID | The type ID of the KItem. | Union[Enum, string] | — | `ktype_id` | Required | +| Slug | A unique slug identifier, minimum 4 characters. | string | `None` | `slug` | Optional | +| Avatar Exists | Whether the KItem holds an avatar or not. | boolean | `False` | `avatar_exists` | Automatically generated | +| Has Contexts | Whether the KItem belongs to at least one context. | boolean | `False` | `has_contexts` | Automatically generated | +| Attachment Extensions | Unique file extensions present in this KItem's attachments. | List[string] | `None` | `attachment_extensions` | Automatically generated | ### Example Usage ```python - item = KItem( name="Glass Bending machine 01", slug="1234", @@ -41,25 +60,25 @@ item = KItem( custom_properties={"location": "Room01", "max_force": "100Pa"}, summary="This is a summary", apps=[ - {"executable": "my_analysis_file", - "title": "Analysis", - "description": "Analysis the tensile strength from machine data"} + { + "executable": "my_analysis_file", + "title": "Analysis", + "description": "Analyse 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=[another_kitem], - user_groups=[ - {"group_id": "33305", "name": "DigiMaterials"} - ] + access_properties={ + "user_access": [{"user_id": "abc-123", "role": 3}], + "group_access": [{"group_id": "g-456", "role": 1}], + }, + schema_data=[ + {"schema_id": "https://example.org/ontology/MyClass", "content": {"key": "value"}} + ], ) ``` @@ -271,6 +290,102 @@ sample_kitem.user_groups = [ ] ``` +## KItemAccessProperties Fields + +`KItemAccessProperties` controls who can access a KItem. It has three orthogonal mechanisms: + +- **Visibility** — a single field that grants read access to broad audiences without requiring explicit role assignments. +- **User access** — per-user role assignments for fine-grained control. +- **Group access** — per-Keycloak-group role assignments. + +### Visibility Values + +| Value | Who can read | +|:-----------:|:-------------------------------------------------:| +| `private` | Only users and groups listed in access properties | +| `internal` | All authenticated users | +| `public` | Everyone (no login required) | + +### Role Values + +| Role name | Integer value | Permitted operations | +|:-------------:|:-------------:|:---------------------------------------------:| +| `MEMBER` | 1 | READ | +| `CONTRIBUTOR` | 2 | READ, UPDATE | +| `OWNER` | 3 | READ, UPDATE, DELETE, MANAGE | + +### KItemAccessProperties Sub-fields + +| Field Name | Description | Type | Default | Property Namespace | Required/Optional | +|:-------------:|:-------------------------------------------------:|:--------------------------------:|:-----------:|:------------------:|:-----------------:| +| Visibility | Broad read-access level | `"private"` \| `"internal"` \| `"public"` | `"private"` | `visibility` | Optional | +| User Access | Per-user role assignments | List[[UserAccessProperty](#useraccessproperty-fields)] | `[]` | `user_access` | Optional | +| Group Access | Per-group role assignments (special visibility groups excluded) | List[[GroupAccessProperty](#groupaccessproperty-fields)] | `[]` | `group_access` | Optional | + +### UserAccessProperty Fields + +| Field Name | Description | Type | Default | Property Namespace | Required/Optional | +|:----------:|:-----------------------:|:------:|:--------------:|:------------------:|:-----------------:| +| User ID | UUID of the user | string | Not Applicable | `user_id` | Required | +| Role | Role assigned to user | int (1–3) or Role name | Not Applicable | `role` | Required | + +### GroupAccessProperty Fields + +| Field Name | Description | Type | Default | Property Namespace | Required/Optional | +|:----------:|:-----------------------:|:------:|:--------------:|:------------------:|:-----------------:| +| Group ID | UUID of the group | string | Not Applicable | `group_id` | Required | +| Role | Role assigned to group | int (1–3) or Role name | Not Applicable | `role` | Required | + +### Example Usage +```python +from dsms.knowledge.properties.access import KItemAccessProperties, Role + +# Make an item readable by all authenticated users, with one explicit owner +item.access_properties = KItemAccessProperties( + visibility="internal", + user_access=[{"user_id": "abc-123", "role": Role.OWNER}], +) + +# Grant a specific group contributor access on a private item +item.access_properties = KItemAccessProperties( + visibility="private", + user_access=[{"user_id": "abc-123", "role": Role.OWNER}], + group_access=[{"group_id": "g-456", "role": Role.CONTRIBUTOR}], +) + +# Query which users can perform a given operation +from dsms.knowledge.properties.access import OperationType +print(item.access_properties.operation_by_user[OperationType.UPDATE]) + +# Look up the minimum role required to delete +from dsms.knowledge.properties.access import RoleMapping +print(RoleMapping.min_access_level(OperationType.DELETE)) # Role.OWNER +``` + +## KItemSchemaData Fields + +`KItemSchemaData` stores a single semantic schema instance attached to a KItem. Each entry maps an ontology-class IRI (the `schema_id`) to a free-form content dictionary. + +| Field Name | Description | Type | Default | Property Namespace | Required/Optional | +|:----------:|:-----------------------------------------------:|:---------------------:|:--------------:|:------------------:|:-----------------:| +| Schema ID | Ontology class IRI that identifies the schema | string | Not Applicable | `schema_id` | Required | +| Content | Free-form instance data for that schema | Dict[string, Any] | `None` | `content` | Optional | + +### Example Usage +```python +item.schema_data = [ + { + "schema_id": "https://example.org/ontology/TensileTest", + "content": {"strain_rate": 0.001, "temperature_K": 293}, + } +] + +# Access by schema ID +from dsms.knowledge.properties.schema_data import KItemSchemaDataList +entries = KItemSchemaDataList(item.schema_data) +test_entry = entries.by_schema_id["https://example.org/ontology/TensileTest"] +``` + ## KItemCustomPropertiesModel | Sub-Property Name | Description | Type | Default | Property Namespace | Required/Optional | @@ -328,15 +443,23 @@ sample_kitem.user_groups = [ ## Widget Fields -| Value | Description | -|:---------------:|:--------------------------------:| -| `Text` | Text input widget | -| `File` | File input widget | -| `Textarea` | Multiline text input widget | -| `Number` | Numeric input widget | -| `Slider` | Slider input widget | -| `Checkbox` | Checkbox input widget | -| `Select` | Dropdown select widget | -| `Radio` | Radio button widget | -| `Knowledge item`| Knowledge item selector widget | -| `Multi-select` | Multi-select widget | +| Value | Description | +|:------------------:|:--------------------------------------------:| +| `Array group` | Repeating group of fields | +| `Checkbox` | Boolean checkbox widget | +| `Date` | Date picker widget | +| `Date-time` | Date and time picker widget | +| `File` | File upload widget | +| `Key-value pairs` | Free-form key/value map widget | +| `Knowledge item` | Knowledge item selector widget | +| `LaTeX` | LaTeX-rendered text widget | +| `Multi-select` | Multi-select dropdown widget | +| `Number` | Numeric input widget | +| `Radio` | Radio button widget | +| `Select` | Dropdown select widget | +| `Slider` | Slider input widget | +| `Star rating` | Star-rating widget | +| `Text` | Single-line text input widget | +| `Textarea` | Multi-line text input widget | +| `URL` | URL input widget | +| `Vocabulary select`| Controlled-vocabulary term selector widget | diff --git a/docs/dsms_sdk/dsms_sdk.md b/docs/dsms_sdk/dsms_sdk.md index 2bb1425..c0edc99 100644 --- a/docs/dsms_sdk/dsms_sdk.md +++ b/docs/dsms_sdk/dsms_sdk.md @@ -18,15 +18,17 @@ 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. +1. Managing Knowledge Items (KItems) — create, update and delete metadata and properties. +2. Contact information and supplementary information for KItems. +3. Semantic annotation of KItems. +4. Attaching semantic schema data (ontology-class instance data) to KItems. +5. Managing Knowledge Types (KTypes), including the v2 semantic-spec subsystem. +6. Role-based access control (RBAC) per KItem: assign users and groups to roles (`MEMBER`, `CONTRIBUTOR`, `OWNER`). +7. Free-text search with filters (KType, annotation, context membership, attachment extension) and a full SPARQL interface including context-scoped queries. +8. Linking KItems to other KItems, and grouping them via context KItems. +9. Linking Apps to KItems, triggered, for example, during a file upload. +10. Performing simple file upload and download of file attachments. +11. 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) @@ -60,7 +62,8 @@ Please take the compability of the SDK version with the DSMS version into accoun | >=3.0.4, <3.1.0 | >=3.0.5, <3.1.0 | | >=3.1.0, <3.2.2 | >=3.1.0, <3.2.1 | | >=3.2.2 | >=3.2.1, <4.0.0 | -| >=4.0.0 | >=4.0.0 | +| >=4.0.0, <5.0.0 | >=4.0.0, <5.0.0 | +| >=5.0.0 | >=5.0.0 | #### Method 1: Via PyPI diff --git a/docs/dsms_sdk/tutorials/1_introduction.ipynb b/docs/dsms_sdk/tutorials/1_introduction.ipynb index d8ddc2e..4dc8c60 100644 --- a/docs/dsms_sdk/tutorials/1_introduction.ipynb +++ b/docs/dsms_sdk/tutorials/1_introduction.ipynb @@ -14,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 [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established 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." ] @@ -22,7 +22,14 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:51:54.747731Z", + "iopub.status.busy": "2026-06-07T20:51:54.747573Z", + "iopub.status.idle": "2026-06-07T20:51:55.425708Z", + "shell.execute_reply": "2026-06-07T20:51:55.424734Z" + } + }, "outputs": [], "source": [ "from dsms import DSMS" @@ -38,10 +45,18 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:51:55.429267Z", + "iopub.status.busy": "2026-06-07T20:51:55.428914Z", + "iopub.status.idle": "2026-06-07T20:51:55.957899Z", + "shell.execute_reply": "2026-06-07T20:51:55.956590Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" ] }, { @@ -61,220 +76,186 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:51:55.961270Z", + "iopub.status.busy": "2026-06-07T20:51:55.961068Z", + "iopub.status.idle": "2026-06-07T20:51:57.124354Z", + "shell.execute_reply": "2026-06-07T20:51:57.123030Z" + } + }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `CKAN Resource`. Cannot check if value is of correct type.\n", - " warnings.warn(\n", - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `CKAN Download URL`. Cannot check if value is of correct type.\n", - " warnings.warn(\n", - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Media Type`. Cannot check if value is of correct type.\n", - " warnings.warn(\n" - ] - }, { "data": { "text/plain": [ "kitems:\n", - "- id: 94f54c99-ddb0-4462-8fe3-06a16aa105ec\n", - " name: Tensile_C_09.lis\n", - " ktype_id: web-ressource\n", - " slug: tensile_c_09lis-94f54c99\n", - " annotations:\n", - " - iri: https://www.iana.org/assignments/media-types/text/csv\n", - " label: csv\n", - " namespace: https://www.iana.org/assignments/media-types/text\n", - " attachments: []\n", - " linked_kitems:\n", - " - is_incoming: false\n", - " label: Has Part\n", - " kitem:\n", - " id: 02ce95a9-50b5-4d79-a68d-49214524aa41\n", - " name: KupferDigital_BAM_Tensile_C_09\n", - " ktype_id: dataset\n", - " slug: kupferdigital_bam_tensile_c_09-02ce95a9\n", - " iri: http://purl.org/dc/terms/hasPart\n", + "- id: 032a3d86-4705-47c0-969e-3251db82a1a6\n", + " name: my tensile test experiment\n", + " ktype_id: dataset\n", + " slug: mytensiletestexperiment-032a3d86\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments:\n", + " - name: dummy_data.csv\n", + " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: true\n", " contacts: []\n", - " created_at: 2025-07-02 15:49:23.206464\n", - " updated_at: 2025-07-02 15:49:23.206464\n", - " external_links:\n", - " - label: CKAN download URL\n", - " url: https://ckan.kupferdigital.org/dataset/02ce95a9-50b5-4d79-a68d-49214524aa41/resource/94f54c99-ddb0-4462-8fe3-06a16aa105ec/download/tensile_c_09.lis\n", - " - label: CKAN source\n", - " url: https://ckan.kupferdigital.org/dataset/02ce95a9-50b5-4d79-a68d-49214524aa41/resource/94f54c99-ddb0-4462-8fe3-06a16aa105ec\n", - " apps: []\n", - " summary: https://ckan.kupferdigital.org/dataset/02ce95a9-50b5-4d79-a68d-49214524aa41/resource/94f54c99-ddb0-4462-8fe3-06a16aa105ec\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: id17514713629953u7k9z\n", - " name: Misc\n", - " entries:\n", - " - id: id1751471362995mom5gj\n", - " type: Text\n", - " label: CKAN Resource\n", - " value: https://ckan.kupferdigital.org/dataset/02ce95a9-50b5-4d79-a68d-49214524aa41/resource/94f54c99-ddb0-4462-8fe3-06a16aa105ec\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id1751471362995wkglsz\n", - " type: Text\n", - " label: CKAN Download URL\n", - " value: https://ckan.kupferdigital.org/dataset/02ce95a9-50b5-4d79-a68d-49214524aa41/resource/94f54c99-ddb0-4462-8fe3-06a16aa105ec/download/tensile_c_09.lis\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " rdf_exists: true\n", + " created_at: 2026-06-05 15:55:53.758604\n", + " updated_at: 2026-06-05 15:55:53.758604\n", + " external_links: []\n", + " apps:\n", + " - executable: testapp2\n", + " title: data2rdf\n", + " description: null\n", + " tags: null\n", + " additional_properties:\n", + " triggerUponUpload: true\n", + " triggerUponUploadFileExtensions:\n", + " - .csv\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []\n", - "- id: 049821c7-2fb4-41b6-89f9-eba2c76d580a\n", - " name: KupferDigital_7F21109_ID3243_Pos._b_EDX\n", - " ktype_id: dataset\n", - " slug: kupferdigital_7f21109_id3243_pos_b_edx-049821c7\n", + "- id: 05884075-236c-483a-8427-1c9cd8886a04\n", + " name: test 2\n", + " ktype_id: metal-sheet\n", + " slug: test-2\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations: []\n", - " attachments: []\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", " linked_kitems:\n", " - is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 3d0822c9-c87c-42c8-b390-8f392ffcea3f\n", - " name: KupferDigi\n", - " ktype_id: web-ressource\n", - " slug: kupferdigi-3d0822c9\n", + " id: 5f2868e2-1196-4fb9-adb7-3b3d68e0a1f3\n", + " name: Specimen Extraction 2618A\n", + " ktype_id: manufacturing-process\n", + " slug: specimen-extraction-2618a\n", + " avatar_exists: true\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", " - is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: f633b4b3-3ca6-447d-9e9b-ed7154611aba\n", - " name: Kupferdigital CKAN Instance\n", - " ktype_id: external-data-source\n", - " slug: kupferdigitalckaninstance-f633b4b3\n", - " iri: http://purl.org/dc/terms/hasPart\n", - " - is_incoming: true\n", - " label: Has Part\n", - " kitem:\n", - " id: 97e4e09b-1c84-428d-a007-f17c9814ad15\n", - " name: KupferDigi\n", - " ktype_id: web-ressource\n", - " slug: kupferdigi-97e4e09b\n", + " id: bb98191c-9695-4980-a655-cad03abde223\n", + " name: Metal sheet test\n", + " ktype_id: metal-sheet\n", + " slug: metal-sheet-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: true\n", " contacts: []\n", - " created_at: 2025-07-02 15:49:37.710531\n", - " updated_at: 2025-07-02 15:49:37.710531\n", - " external_links:\n", - " - label: CKAN source\n", - " url: https://ckan.kupferdigital.org/dataset/049821c7-2fb4-41b6-89f9-eba2c76d580a\n", + " created_at: 2026-06-04 18:35:46.248292\n", + " updated_at: 2026-06-04 18:35:46.248292\n", + " external_links: []\n", " apps: []\n", - " summary: eds measurements on a diffusion specimen\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: id1751471401085iwiex6\n", - " name: Misc\n", - " entries:\n", - " - id: id1751471401085lwpseh\n", - " type: Knowledge item\n", - " label: Resources\n", - " value:\n", - " - id: 3d0822c9-c87c-42c8-b390-8f392ffcea3f\n", - " name: KupferDigi\n", - " ktype_id: web-ressource\n", - " slug: kupferdigi-3d0822c9\n", - " - id: 97e4e09b-1c84-428d-a007-f17c9814ad15\n", - " name: KupferDigi\n", - " ktype_id: web-ressource\n", - " slug: kupferdigi-97e4e09b\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id17514714010858safjx\n", - " type: Knowledge item\n", - " label: Publishing Organisation\n", - " value:\n", - " - id: c6a0740a-3bb2-4661-a5d7-67488fa061a7\n", - " name: fem-organization\n", - " ktype_id: organization\n", - " slug: fem-organization-c6a0740a\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " rdf_exists: true\n", - " contexts: []\n", - "- id: 3d0822c9-c87c-42c8-b390-8f392ffcea3f\n", - " name: KupferDigi\n", - " ktype_id: web-ressource\n", - " slug: kupferdigi-3d0822c9\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " - role: CONTRIBUTOR\n", + " user_id: b76fa8d6-20d8-4a7e-a959-a975082b1c63\n", + " group_access: []\n", + " contexts:\n", + " - id: 249e1cdf-f584-4568-86cb-198757c66c15\n", + " name: metal sheet batch test\n", + " ktype_id: metal-sheet-batch\n", + " slug: metal-sheet-batch-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + "- id: 1053cd9b-221a-40cc-adb6-517cdcaa174c\n", + " name: My creep speciment\n", + " ktype_id: creep-specimen\n", + " slug: my-creep-speciment\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations:\n", - " - iri: https://www.iana.org/assignments/media-types/application/vnd.ms-excel\n", - " label: vnd.ms-excel\n", - " namespace: https://www.iana.org/assignments/media-types/application\n", - " attachments: []\n", - " linked_kitems:\n", - " - is_incoming: true\n", - " label: Has Part\n", - " kitem:\n", - " id: 049821c7-2fb4-41b6-89f9-eba2c76d580a\n", - " name: KupferDigital_7F21109_ID3243_Pos._b_EDX\n", - " ktype_id: dataset\n", - " slug: kupferdigital_7f21109_id3243_pos_b_edx-049821c7\n", - " iri: http://purl.org/dc/terms/hasPart\n", + " - iri: https://w3id.org/steel/ProcessOntology/PercentageExtension\n", + " label: PercentageExtension\n", + " namespace: steelontology\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", + " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: true\n", " contacts: []\n", - " created_at: 2025-07-02 15:49:49.501334\n", - " updated_at: 2025-07-02 15:49:49.501334\n", - " external_links:\n", - " - label: CKAN source\n", - " url: https://ckan.kupferdigital.org/dataset/049821c7-2fb4-41b6-89f9-eba2c76d580a/resource/3d0822c9-c87c-42c8-b390-8f392ffcea3f\n", - " - label: CKAN download URL\n", - " url: https://ckan.kupferdigital.org/dataset/049821c7-2fb4-41b6-89f9-eba2c76d580a/resource/3d0822c9-c87c-42c8-b390-8f392ffcea3f/download/kupferdigital_7f21109_id3243_pos._b_edx_rawdata.xlsx\n", + " created_at: 2026-05-28 08:49:52.034919\n", + " updated_at: 2026-05-28 08:49:52.034919\n", + " external_links: []\n", " apps: []\n", - " summary: https://ckan.kupferdigital.org/dataset/049821c7-2fb4-41b6-89f9-eba2c76d580a/resource/3d0822c9-c87c-42c8-b390-8f392ffcea3f\n", - " user_groups: []\n", + " summary: ''\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: id1751471389254oxwoza\n", - " name: Misc\n", + " - id: section-creep-gauge\n", + " name: Gauge Section\n", " entries:\n", - " - id: id1751471389254g2ojkx\n", - " type: Text\n", - " label: CKAN Resource\n", - " value: https://ckan.kupferdigital.org/dataset/049821c7-2fb4-41b6-89f9-eba2c76d580a/resource/3d0822c9-c87c-42c8-b390-8f392ffcea3f\n", + " - id: input-creep-gauge-length\n", + " type: Number\n", + " label: Gauge length\n", + " value: null\n", " measurementUnit: null\n", - " relationMapping: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasGaugeLengthLo_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", " required: false\n", - " - id: id1751471389254kf8g0k\n", - " type: Text\n", - " label: CKAN Download URL\n", - " value: https://ckan.kupferdigital.org/dataset/049821c7-2fb4-41b6-89f9-eba2c76d580a/resource/3d0822c9-c87c-42c8-b390-8f392ffcea3f/download/kupferdigital_7f21109_id3243_pos._b_edx_rawdata.xlsx\n", + " - id: input-creep-gauge-diameter\n", + " type: Number\n", + " label: Gauge diameter\n", + " value: null\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDiameter_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: section-creep-grip\n", + " name: Grip Ends\n", + " entries:\n", + " - id: input-creep-grip-type\n", + " type: Select\n", + " label: Grip type\n", + " value: null\n", " measurementUnit: null\n", - " relationMapping: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleType_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", " required: false\n", - " - id: id1751471389254gdr583\n", + " - id: input-creep-thread-spec\n", " type: Text\n", - " label: Media Type\n", - " value: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\n", + " label: Thread specification\n", + " value: null\n", " measurementUnit: null\n", - " relationMapping: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleInfos_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", " required: false\n", - " rdf_exists: false\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []\n", - "total_count: 356" + "total_count: 38" ] }, "execution_count": 3, @@ -300,251 +281,3621 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:51:57.152498Z", + "iopub.status.busy": "2026-06-07T20:51:57.152317Z", + "iopub.status.idle": "2026-06-07T20:51:57.279821Z", + "shell.execute_reply": "2026-06-07T20:51:57.278583Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ktype:\n", - " id: expert\n", - " created_at: '2025-06-26T13:27:43.597461'\n", - " updated_at: '2025-06-26T13:27:43.597461'\n", - "\n", - "ktype:\n", - " id: dataset\n", - " name: dataset\n", - " created_at: '2025-06-26T13:27:43.597461'\n", - " updated_at: '2025-06-26T13:27:43.597461'\n", - "\n", "ktype:\n", " id: dataset-catalog\n", " name: dataset catalog\n", - " created_at: '2025-06-26T13:27:43.597461'\n", - " updated_at: '2025-06-26T13:27:43.597461'\n", + " created_at: '2024-07-28T18:48:21.024154'\n", + " updated_at: '2024-07-28T18:48:21.024154'\n", "\n", "ktype:\n", - " id: external-data-source\n", - " name: External Data Source\n", - " created_at: '2025-07-02T09:43:27.121927'\n", - " updated_at: '2025-07-02T09:43:27.121927'\n", + " id: dataset\n", + " name: Dataset\n", + " process_schema_id: d5def9c4-edd2-491e-ae32-b99e3c0798c1\n", + " process_schema:\n", + " id: d5def9c4-edd2-491e-ae32-b99e3c0798c1\n", + " name: daaad\n", + " spec:\n", + " - id: engineered-raw-material\n", + " label: Engineered raw material\n", + " is_child: false\n", + " mappings: []\n", + " children: []\n", + " created_at: 2025-08-15 14:24:31.597368\n", + " updated_at: 2025-08-15 14:24:31.597368\n", + " created_at: '2024-07-28T18:48:21.024154'\n", + " updated_at: '2026-05-13T10:16:27.749520'\n", "\n", "ktype:\n", - " id: app\n", - " name: app\n", - " webform_schema_id: a366408f-7949-42ac-8e9b-686f788cbd86\n", + " id: chemical-element\n", + " name: Chemical element\n", + " webform_schema_id: dccbd36b-33c7-4300-b29a-c8c42be0103d\n", " webform_schema:\n", - " id: a366408f-7949-42ac-8e9b-686f788cbd86\n", - " name: application\n", + " id: dccbd36b-33c7-4300-b29a-c8c42be0103d\n", + " name: Chemical element\n", " spec:\n", " semantics_enabled: true\n", " sections_enabled: false\n", " class_mapping:\n", - " - https://w3id.org/emmo#EMMO_3b031fa9_8623_4ea5_8b57_bcafb70c5c8b\n", + " - https://w3id.org/emmo#EMMO_4f40def1_3cd7_4067_9596_541e9a5134cf\n", " sections:\n", - " - id: id11ee168baf44a8\n", - " name: ''\n", + " - id: id1081805716ece8\n", + " name: Untitled Section\n", " inputs:\n", - " - id: idf0096545188b98\n", - " label: Application name\n", + " - id: id2432a7770fd35\n", + " label: Symbol\n", " widget: Text\n", " required: false\n", + " hint: Symbol of the chemical element\n", " hidden: false\n", " ignore: false\n", " select_options: []\n", " relation_mapping:\n", - " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier\n", - " label: has Identifier\n", - " type: data_property\n", + " iri: https://w3id.org/emmo#EMMO_79c0edfa_06f9_5149_b754_28c589035b8a\n", + " type: object_property\n", + " class_iri: https://w3id.org/emmo#EMMO_d357e0dd_3497_4590_af6f_7954db7fecf7\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idd1d4b6bc1d88f8\n", + " label: Mass density\n", + " widget: Number\n", + " required: false\n", + " hint: \"Mass density of the pure element at 20 \\xB0C\"\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: g/cm^3\n", + " namespace: None\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " range_options:\n", + " min: 0\n", + " max: 100\n", + " step: 1\n", + " range: false\n", + " - id: id8947278e4717e\n", + " label: Atomic weigth\n", + " widget: Number\n", + " required: false\n", + " hint: Weigth of a single atom of the element\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: u\n", + " namespace: None\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " range_options:\n", + " min: 0\n", + " max: 100\n", + " step: 1\n", + " range: false\n", + " - id: id5afa099e502808\n", + " label: Electronegativity\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " range_options:\n", + " min: 0\n", + " max: 100\n", + " step: 1\n", + " range: false\n", + " - id: id9ca4bd4c66c7d8\n", + " label: Atomic number\n", + " widget: Number\n", + " required: false\n", + " hint: Nuclear charge number (symbol Z) of a chemical element is the charge\n", + " number of an atomic nucleus.\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " range_options:\n", + " min: 0\n", + " max: 100\n", + " step: 1\n", + " range: false\n", + " - id: id8ff58e11b41d1\n", + " label: Category\n", + " widget: Select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Alkali metals\n", + " disabled: false\n", + " - key: option2\n", + " label: Alkaline earth metals\n", + " disabled: false\n", + " - key: option3\n", + " label: Transition elements\n", + " disabled: false\n", + " - key: option4\n", + " label: Pnictogens\n", + " disabled: false\n", + " - key: option5\n", + " label: Chalcogens\n", + " disabled: false\n", + " - key: option6\n", + " label: Halogens\n", + " disabled: false\n", + " - key: option7\n", + " label: Noble gases\n", + " disabled: false\n", + " - key: option8\n", + " label: Lanthanoids\n", + " disabled: false\n", + " - key: option9\n", + " label: Actinoids\n", + " disabled: false\n", + " - key: option10\n", + " label: Rare-earth metals\n", + " disabled: false\n", + " - key: option11\n", + " label: Inner transition elements\n", + " disabled: false\n", + " - key: option12\n", + " label: Nonmetals\n", + " disabled: false\n", + " - key: option13\n", + " label: Main-group elements\n", + " disabled: false\n", + " - key: option14\n", + " label: Metals\n", + " disabled: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " range_options:\n", + " min: 0\n", + " max: 100\n", + " step: 1\n", + " range: false\n", + " - id: id6723afc60ded7\n", + " label: CAS registry number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_5d73661e_e710_4844_ab9b_a85b7e68576a\n", + " type: object_property\n", + " class_iri: https://w3id.org/ORCHESTER/CAS-Number\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: idcb35894915be5\n", + " label: Critical raw material according to EU definition 2023\n", + " widget: Checkbox\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_5d73661e_e710_4844_ab9b_a85b7e68576a\n", + " type: object_property\n", + " class_iri: https://w3id.org/ORCHESTER/CriticalRawMaterial\n", + " inverse: false\n", " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", " hidden: false\n", - " created_at: '2025-07-15T14:28:48.236596'\n", - " updated_at: '2025-07-15T14:28:48.236596'\n", - " created_at: '2025-06-26T13:27:43.597461'\n", - " updated_at: '2025-07-15T14:29:07.123972'\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-10-23T12:27:02.976940'\n", + " updated_at: '2025-03-30T08:39:19.556285'\n", "\n", "ktype:\n", - " id: web-ressource\n", - " name: Web Ressource\n", - " webform_schema_id: 6f897085-20ae-4f58-88cb-4ecfaa59e251\n", + " id: engineered-raw-material\n", + " name: Engineered raw material\n", + " webform_schema_id: 2a6bfb9f-0e94-4a34-933f-91d99f5475dc\n", " webform_schema:\n", - " id: 6f897085-20ae-4f58-88cb-4ecfaa59e251\n", - " name: Web Ressource\n", + " id: 2a6bfb9f-0e94-4a34-933f-91d99f5475dc\n", + " name: Engineered raw material\n", " spec:\n", " semantics_enabled: true\n", - " sections_enabled: false\n", + " sections_enabled: true\n", " class_mapping:\n", - " - http://www.w3.org/ns/dcat#Resource\n", + " - https://w3id.org/pmd/co/EngineeredMaterial\n", " sections:\n", - " - id: id34da047a592c98\n", + " - id: idc4618e5992561\n", " name: Untitled Section\n", " inputs:\n", - " - id: id053f055e718bf8\n", - " label: Access URL\n", + " - id: id8668ff07ea324\n", + " label: Name of the raw material\n", " widget: Text\n", " required: false\n", " hidden: false\n", " ignore: false\n", " select_options: []\n", " relation_mapping:\n", - " iri: http://www.w3.org/ns/dcat#accessURL\n", - " label: access address\n", - " type: property\n", + " iri: https://orchester.materials-data.space/ontology/eclass/HasBezeichnung\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: idc6aa4925f283b\n", + " name: 'Material classification '\n", + " inputs:\n", + " - id: id0eb116104e865\n", + " label: CAS number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasMaterialIdentifier_Object\n", + " type: object_property\n", + " class_iri: https://w3id.org/ORCHESTER/CAS-Number\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id2a6a94b7a6b86\n", + " label: Material class ID according to IEC 62474\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasMaterialIdentifier_Object\n", + " type: object_property\n", + " class_iri: https://w3id.org/ORCHESTER/IEC-62474-MaterialClassificationNumber\n", + " inverse: false\n", " multiple_selection: false\n", " hidden: false\n", - " created_at: '2025-07-15T13:58:53.020266'\n", - " updated_at: '2025-07-16T11:52:10.690939'\n", - " created_at: '2025-07-02T09:42:46.289536'\n", - " updated_at: '2025-07-16T11:51:03.826524'\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-27T06:58:34.114685'\n", + " updated_at: '2025-03-22T08:22:07.956056'\n", "\n", "ktype:\n", - " id: organization\n", - " name: organization\n", - " webform_schema_id: 0575cff0-e6d3-45ca-8474-ac5b402e522d\n", + " id: app\n", + " name: App\n", + " webform_schema_id: 7ef1f83c-82c5-4adb-bafb-da7f28bcbaa2\n", " webform_schema:\n", - " id: 0575cff0-e6d3-45ca-8474-ac5b402e522d\n", - " name: organization\n", + " id: 7ef1f83c-82c5-4adb-bafb-da7f28bcbaa2\n", + " name: app\n", " spec:\n", " semantics_enabled: true\n", " sections_enabled: false\n", - " class_mapping: []\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/ApplicationProgram\n", " sections:\n", - " - id: id318659d85714d8\n", - " name: ''\n", + " - id: ida00a19653fc4\n", + " name: Untitled Section\n", " inputs:\n", - " - id: idba9fb3fd5543a8\n", - " label: address\n", - " widget: Text\n", + " - id: id3be2dc8a3eb2b\n", + " label: Type of application\n", + " widget: Multi-select\n", " required: false\n", " hidden: false\n", " ignore: false\n", - " select_options: []\n", + " select_options:\n", + " - key: option1\n", + " label: Web application\n", + " disabled: false\n", + " - key: option2\n", + " label: Software application\n", + " disabled: false\n", + " - key: option3\n", + " label: Data processing application\n", + " disabled: false\n", " relation_mapping:\n", - " iri: https://w3id.org/steel/ProcessOntology/hasLocation\n", - " type: property\n", + " iri: https://w3id.org/ORCHESTER/HasApplicationType\n", + " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", " hidden: false\n", - " created_at: '2025-07-16T14:18:05.866631'\n", - " updated_at: '2025-07-16T14:18:05.866631'\n", - " created_at: '2025-07-16T14:18:05.992083'\n", - " updated_at: '2025-07-16T14:18:06.051750'\n", - "\n", - "ktype:\n", - " id: characterization-process\n", - " name: Characterization Process\n", - " process_schema_id: 4975067f-4804-4218-bf83-e48ff3990281\n", - " process_schema:\n", - " id: 4975067f-4804-4218-bf83-e48ff3990281\n", - " name: Characterization Process\n", - " spec:\n", - " - id: organization\n", - " label: organization\n", - " is_child: false\n", - " mappings:\n", - " - dst_ktype_id: app\n", - " relation_iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " relation_name: hasPart\n", - " children: []\n", - " - id: app\n", - " label: app\n", - " is_child: false\n", - " mappings:\n", - " - dst_ktype_id: web-ressource\n", - " relation_iri: https://w3id.org/emmo#EMMO_c4bace1d_4db0_4cd3_87e9_18122bae2840\n", - " relation_name: hasOutput\n", - " children:\n", - " - id: web-ressource\n", - " label: Web Ressource\n", - " is_child: true\n", - " mappings: []\n", - " children: []\n", - " created_at: 2025-07-16 14:18:06.312271\n", - " updated_at: 2025-07-16 14:18:06.312271\n", - " created_at: '2025-07-16T14:18:06.433199'\n", - " updated_at: '2025-07-16T14:18:06.495714'\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-07-28T18:48:21.024154'\n", + " updated_at: '2026-05-03T13:55:53.473588'\n", "\n", "ktype:\n", - " id: specimen\n", - " name: Specimen\n", - " webform_schema_id: 21164fb6-cc45-4e08-8f8b-467a749df54b\n", + " id: production-process\n", + " name: Production process\n", + " webform_schema_id: eafb9978-2fd7-4d1a-86f7-0d3c9b10f784\n", " webform_schema:\n", - " id: 21164fb6-cc45-4e08-8f8b-467a749df54b\n", - " name: Specimen\n", + " id: eafb9978-2fd7-4d1a-86f7-0d3c9b10f784\n", + " name: Production process\n", " spec:\n", " semantics_enabled: true\n", - " sections_enabled: false\n", + " sections_enabled: true\n", " class_mapping:\n", - " - https://w3id.org/pmd/co/Specimen\n", + " - https://w3id.org/ORCHESTER/ProductionProcess\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: id474a0e2154dfa\n", + " name: Basic information\n", " inputs:\n", - " - id: id6c76bbffe7ca78\n", - " label: Width\n", - " widget: Number\n", + " - id: id54f8f37ec00d5\n", + " label: Name of the production process\n", + " widget: Text\n", " required: false\n", " hidden: false\n", " ignore: false\n", " select_options: []\n", - " measurement_unit:\n", - " label: Millimetre\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " namespace: http://qudt.org/vocab/unit\n", " relation_mapping:\n", - " iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " type: object_property\n", - " class_iri: https://w3id.org/emmo#EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0\n", + " iri: https://orchester.materials-data.space/ontology/eclass/HasBezeichnung\n", + " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", " knowledge_type:\n", " - null\n", - " range_options:\n", - " min: 0\n", - " max: 1\n", - " step: 0.1\n", - " range: false\n", - " - id: id717d07130a7618\n", - " label: Length\n", - " widget: Slider\n", + " - id: id0c4d8631681ea\n", + " label: Production device\n", + " widget: Knowledge item\n", " required: false\n", " hidden: false\n", " ignore: false\n", " select_options: []\n", - " measurement_unit:\n", - " label: Millimetre\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " namespace: http://qudt.org/vocab/unit\n", " relation_mapping:\n", - " iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " type: object_property\n", - " class_iri: https://w3id.org/emmo#EMMO_cd2cd0de_e0cc_4ef1_b27e_2e88db027bac\n", - " relation_mapping_extra:\n", - " iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " type: object_property\n", - " class_iri: https://w3id.org/emmo#EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0\n", + " iri: https://w3id.org/ORCHESTER/HasDevice\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type:\n", + " - production-device\n", + " - id: idac65a34241e1b\n", + " label: Operator\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasOperator\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type:\n", + " - expert\n", + " hidden: false\n", + " - id: id6165143256806\n", + " name: Input\n", + " inputs:\n", + " - id: id98a91c57b9725\n", + " label: Feedstock\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasInput\n", + " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", - " range_options:\n", - " min: 0\n", - " max: 1\n", - " step: 0.1\n", - " range: true\n", + " knowledge_type:\n", + " - engineered-raw-material\n", + " - srap\n", + " - engineered-material\n", " hidden: false\n", - " created_at: '2025-07-21T09:24:48.597715'\n", - " updated_at: '2025-07-21T13:56:38.225444'\n", - " created_at: '2025-07-21T09:20:12.746430'\n", - " updated_at: '2025-07-21T09:24:58.495720'\n", - "\n", - "ktype:\n", - " id: testingmachine\n", - " name: TestingMachine\n", - " created_at: '2025-07-22T12:41:50.505566'\n", - " updated_at: '2025-07-22T12:41:50.505566'\n", - "\n" - ] - } - ], - "source": [ - "for ktype in dsms.ktypes:\n", - " print(ktype)" + " - id: ide70ac894011e5\n", + " name: Output\n", + " inputs:\n", + " - id: id046b57d591547\n", + " label: Production result (Output)\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasOutput\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - engineered-raw-material\n", + " hidden: false\n", + " - id: id0b092428a801f\n", + " name: Process parameters\n", + " inputs:\n", + " - id: iddde1dea6f3929\n", + " label: Target process parameters\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/parameters\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - dataset\n", + " - id: id364aaa5f47529\n", + " label: Target process parameters (freetext)\n", + " widget: Textarea\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/parameters\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id91b64ed5b453e\n", + " label: Process data\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/parameters\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type:\n", + " - dataset\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-27T00:37:48.409608'\n", + " updated_at: '2025-03-29T16:37:41.001343'\n", + "\n", + "ktype:\n", + " id: specimen\n", + " name: Specimen\n", + " webform_schema_id: 7199e339-2512-4aed-8b99-af495c0349d3\n", + " webform_schema:\n", + " id: 7199e339-2512-4aed-8b99-af495c0349d3\n", + " name: Specimen\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping: http://purl.obolibrary.org/obo/OBI_0100051\n", + " sections:\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", + " inputs:\n", + " - id: input-specimen-type\n", + " label: Specimen type\n", + " widget: Text\n", + " required: false\n", + " hint: e.g. flat, round, notched\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleType_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-geometry\n", + " label: Specimen geometry\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceGeometry_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-width\n", + " label: Width\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasWidth_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-length\n", + " label: Length\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceLength_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-thickness\n", + " label: Thickness\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceThickness_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-diameter\n", + " label: Diameter\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDiameter_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-description\n", + " label: Additional description\n", + " widget: Textarea\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleInfos_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2026-06-04T16:44:35.090388'\n", + " created_at: '2024-10-23T13:59:30.448842'\n", + " updated_at: '2024-11-15T11:28:54.289602'\n", + "\n", + "ktype:\n", + " id: material-model\n", + " name: Material model\n", + " webform_schema_id: 615d6973-8911-4d48-92fa-a52f40cfbc4b\n", + " webform_schema:\n", + " id: 615d6973-8911-4d48-92fa-a52f40cfbc4b\n", + " name: Material model\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/material-model/MaterialModel\n", + " sections:\n", + " - id: id8fb799b97436a\n", + " name: Basic information\n", + " inputs:\n", + " - id: ida80c8421ee611\n", + " label: Name of the material model\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idaa801d028b0fe\n", + " label: Source document\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#titleWithOtherTitleInformation\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: document\n", + " - id: id0f16b1c4a9c\n", + " label: Short description\n", + " widget: Textarea\n", + " required: false\n", + " hint: Please add a short description of the material model\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://purl.org/dc/terms/description\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idb008410d0bcba\n", + " label: Geometrical linear or non-linear\n", + " widget: Select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Small strain theory\n", + " disabled: false\n", + " - key: option2\n", + " label: Large strain theory\n", + " disabled: false\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasModelProperty\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ida2ee67091b30a\n", + " label: Rate dependent of rate in-dependent\n", + " widget: Select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: 'Rate dependent '\n", + " disabled: false\n", + " - key: option2\n", + " label: Rate independent\n", + " disabled: false\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasModelProperty\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ida13da14f81332\n", + " label: Thermodynamic theory\n", + " widget: Select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: None\n", + " disabled: false\n", + " - key: option2\n", + " label: Rational Thermodynamic\n", + " disabled: false\n", + " - key: option3\n", + " label: Extended Rational Thermodynamic\n", + " disabled: false\n", + " - key: option4\n", + " label: Other thermodynamic theory\n", + " disabled: false\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasModelProperty\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: id48ce5b50ef83b\n", + " name: Field of Application\n", + " inputs: []\n", + " hidden: false\n", + " - id: id16ad89c297348\n", + " name: ''\n", + " inputs: []\n", + " hidden: false\n", + " - id: id260478fe428a8\n", + " name: Material parameters\n", + " inputs: []\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-01-25T11:17:26.603921'\n", + " updated_at: '2025-03-12T23:21:48.982242'\n", + "\n", + "ktype:\n", + " id: magnetic-material\n", + " name: Magnetic material\n", + " webform_schema_id: 3a7d595a-986c-4fba-954f-2ff43caca31b\n", + " webform_schema:\n", + " id: 3a7d595a-986c-4fba-954f-2ff43caca31b\n", + " name: Magnetic material\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/MagneticMaterial\n", + " sections:\n", + " - id: id9d3c2a3a2186c8\n", + " name: Fundamental information\n", + " inputs:\n", + " - id: iddbebcb021cb248\n", + " label: 'Name of the magnetic material '\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " hidden: false\n", + " - id: id2a622413d4c5e\n", + " name: Composition\n", + " inputs:\n", + " - id: idccdea2180ffb78\n", + " label: Compostion (standard notation)\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " hidden: false\n", + " - id: ide01f38b85a8938\n", + " name: Magnetic Properties\n", + " inputs:\n", + " - id: id3511dc5260d8c8\n", + " label: Maximum energy product (BH)_max\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Joule per Cubic Metre\n", + " iri: http://qudt.org/vocab/unit/J-PER-M3\n", + " namespace: http://qudt.org/vocab/unit\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-04-08T15:26:04.858714'\n", + " updated_at: '2025-04-08T15:34:36.094374'\n", + "\n", + "ktype:\n", + " id: srap\n", + " name: Scrap batch\n", + " webform_schema_id: bf25a6e0-ed75-4ab2-b874-2ff02e38e0bd\n", + " webform_schema:\n", + " id: bf25a6e0-ed75-4ab2-b874-2ff02e38e0bd\n", + " name: Scrap batch\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/ScrapBatch\n", + " sections:\n", + " - id: id24bde5f43ae78\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: idc561b2cb686e2\n", + " label: Scrap type\n", + " widget: Multi-select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Aluminium scrap\n", + " disabled: false\n", + " - key: option2\n", + " label: Steel scrap\n", + " disabled: false\n", + " - key: option3\n", + " label: Magnet scrap\n", + " disabled: false\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ScrapType\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id2e22f0e9c7cac\n", + " label: Weight\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: kilogram\n", + " iri: http://qudt.org/vocab/unit/KiloGM\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_5d73661e_e710_4844_ab9b_a85b7e68576a\n", + " type: object_property\n", + " class_iri: https://w3id.org/pmd/co/Weight\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-27T00:11:53.457843'\n", + " updated_at: '2025-03-09T07:44:46.178104'\n", + "\n", + "ktype:\n", + " id: production-process-chain\n", + " name: Production process chain\n", + " webform_schema_id: 7b5e4346-6660-41e9-a066-df6ce9e83144\n", + " webform_schema:\n", + " id: 7b5e4346-6660-41e9-a066-df6ce9e83144\n", + " name: Production process chain\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/ProductionProcessChain\n", + " sections:\n", + " - id: id2e6006d579c25\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: idb9c8b50c13c02\n", + " label: Involved production processes\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: production-process\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-03-03T09:16:17.824248'\n", + " updated_at: '2025-03-03T09:20:04.500849'\n", + "\n", + "ktype:\n", + " id: engineered-material\n", + " name: Engineered material\n", + " webform_schema_id: 5241d526-156d-481e-8412-cf21e0f5842f\n", + " webform_schema:\n", + " id: 5241d526-156d-481e-8412-cf21e0f5842f\n", + " name: Engineered material\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " sections:\n", + " - id: id94fcf64304dcf8\n", + " name: Untitled\n", + " inputs:\n", + " - id: idc433348a844ea\n", + " label: Material number\n", + " widget: Text\n", + " required: false\n", + " hint: Material number (Werkstoffnummer) after EN 10027-2:1992-09\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id5ec90ef5aca04\n", + " label: Material abbreviation\n", + " widget: Text\n", + " required: false\n", + " hint: e.g. X6CrNiTi18-10\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id90b1a7433f4f28\n", + " label: Minimum Tensile strength\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: N/mm^2\n", + " namespace: None\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id991fbd92598258\n", + " label: AISI designation\n", + " widget: Text\n", + " required: false\n", + " hint: Classification after the American Iron and Steel Institute\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id7f0f1cb64a93f8\n", + " label: Recommended fillter metal\n", + " widget: Text\n", + " required: false\n", + " hint: 'material suitable for joint welding, e.g. 4430 '\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id2bc92ba6732978\n", + " label: Maximum annealed core hardness\n", + " widget: Number\n", + " required: false\n", + " hint: HB (Brinell Hardness)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id663bfcfaf22cb\n", + " label: Machining\n", + " widget: Number\n", + " required: false\n", + " hint: 'at an advance of 0,4 mm/r '\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: mm/min\n", + " namespace: None\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: ideb3fbffa636fd\n", + " label: Maximum recommended application temperature\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: \"\\xB0C\"\n", + " namespace: None\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id2a8a4eaf8476d8\n", + " label: Achievable strength class\n", + " widget: Text\n", + " required: false\n", + " hint: A=S235, B=S275, C=S355, D=S460, E=S690, F = S880\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id2394fdbef80ce\n", + " label: Pressure vessel construction approved\n", + " widget: Checkbox\n", + " required: false\n", + " value: false\n", + " hint: after EN-DIN 10272\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-10-23T12:43:28.801406'\n", + " updated_at: '2024-10-23T13:01:04.742102'\n", + "\n", + "ktype:\n", + " id: characterization-process\n", + " name: Characterization Process\n", + " webform_schema_id: e38cff0d-c7fb-43c3-ad50-383c5e5ecfc0\n", + " webform_schema:\n", + " id: e38cff0d-c7fb-43c3-ad50-383c5e5ecfc0\n", + " name: Characterization Process\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " sections:\n", + " - id: id70b5ab713ca8d\n", + " name: Untitled\n", + " inputs:\n", + " - id: id61800fb15eea48\n", + " label: Start time\n", + " widget: Text\n", + " required: false\n", + " hint: When the process was initalized\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: dd/mm/yyyy\n", + " namespace: None\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTimeStamp_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id007382f9957a7\n", + " label: Completion time\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: dd/mm/yyyy\n", + " namespace: None\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTimeStamp_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: idc7727de56797e\n", + " label: Process description\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasRemark_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id3986828345c24\n", + " label: Name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id8355485bdc96f\n", + " label: Name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id880a6e061da58\n", + " label: Name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id73697ca2c6e228\n", + " label: Identifier\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-11-13T13:39:51.818431'\n", + " updated_at: '2024-11-15T11:40:00.136101'\n", + "\n", + "ktype:\n", + " id: act\n", + " name: Act\n", + " webform_schema_id: b8e559ef-ff1e-460d-85f4-f163322da908\n", + " webform_schema:\n", + " id: b8e559ef-ff1e-460d-85f4-f163322da908\n", + " name: Act\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/Act\n", + " sections:\n", + " - id: id17c04e08e1ce7\n", + " name: ''\n", + " inputs:\n", + " - id: id6082fe47586758\n", + " label: Title of the Act\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#publication\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idf9d59f1110cc18\n", + " label: Date of publication\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#dateOfPublication\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-04-05T15:26:42.185420'\n", + " updated_at: '2025-04-05T15:26:42.185420'\n", + "\n", + "ktype:\n", + " id: production-device\n", + " name: Production device\n", + " webform_schema_id: d1989a12-a382-468d-a033-73b7ee692f09\n", + " webform_schema:\n", + " id: d1989a12-a382-468d-a033-73b7ee692f09\n", + " name: Production device\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/emmo#EMMO_256bb4be_78c6_4f2f_8589_f5e4c8339bbd\n", + " sections:\n", + " - id: idacaf49aabe4d8\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: idb37d85da854f8\n", + " label: Name of the production device\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasProductionDeviceName\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id69ad9baba47c\n", + " label: Manufacturer\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo/domain/characterisation-methodology/chameo#hasManufacturer\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id69221805915f5\n", + " label: Year of manufacture\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#dateOfProduction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id2f4fcc96caf08\n", + " label: Production device type\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ProcuctionDeviceType\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id5de744587dc7e\n", + " label: Serial Number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasSerialNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id6c3f63c77fdac\n", + " label: Responsible organization for the device\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasResponsibleOrganizationUnit\n", + " type: object_property\n", + " class_iri: https://w3id.org/emmo#EMMO_c0f72631_d7c2_434c_9c26_5c44123df682\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - organization\n", + " - id: id0cb4a7a37833e\n", + " label: Room number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasRoomNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: idc9875a15a8b2d\n", + " label: Inventory number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasInventoryNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: idc20ea1cef3cb4\n", + " label: Contact person\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_1246b120_abbe_4840_b0f8_3e4348b24a17\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - expert\n", + " - id: idfa6adf1c18178\n", + " label: Operation range\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/OperationRange\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-26T18:15:41.787658'\n", + " updated_at: '2025-04-10T00:20:37.744549'\n", + "\n", + "ktype:\n", + " id: emmc-workshop\n", + " name: EMMC-Workshop\n", + " webform_schema_id: efb66b2f-1d58-43eb-b733-f18ae8ecf507\n", + " webform_schema:\n", + " id: efb66b2f-1d58-43eb-b733-f18ae8ecf507\n", + " name: EMMC-Workshop\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/EMMC-Workshop\n", + " sections:\n", + " - id: id48cdfd3a4b8fc8\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: idb524743c8bbab8\n", + " label: Year\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDeliveryDate\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-04-10T08:43:14.129840'\n", + " updated_at: '2025-04-10T08:43:59.914287'\n", + "\n", + "ktype:\n", + " id: silver-palladium-alloy\n", + " name: Silver Palladium Alloy\n", + " webform_schema_id: fdea16d4-7b8c-4ad9-9def-53f0b1120958\n", + " webform_schema:\n", + " id: fdea16d4-7b8c-4ad9-9def-53f0b1120958\n", + " name: Silver Palladium Alloy\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/pmd/co/ChemicalComposition\n", + " sections:\n", + " - id: idf65262631f6ee\n", + " name: Composition\n", + " inputs:\n", + " - id: id845324b7b61f8\n", + " label: 'Silver '\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasSilverMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idb0b164af3d40c\n", + " label: Palladium\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasPalladiumMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: idcd5897611d475\n", + " name: Physical properties\n", + " inputs:\n", + " - id: id7f9b735518b03\n", + " label: Density\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Gram Per Cubic Centimetre\n", + " iri: http://qudt.org/vocab/unit/GM-PER-CentiM3\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_5d73661e_e710_4844_ab9b_a85b7e68576a\n", + " type: object_property\n", + " class_iri: https://w3id.org/steel/ProcessOntology/MassDensity\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id55fb4d9662696\n", + " label: Melting point of range\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: degree Celsius\n", + " iri: http://qudt.org/vocab/unit/DEG_C\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_5d73661e_e710_4844_ab9b_a85b7e68576a\n", + " type: object_property\n", + " class_iri: https://w3id.org/pmd/co/Temperature\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: -273\n", + " max: 2000\n", + " step: 0.1\n", + " range: false\n", + " hidden: false\n", + " - id: id702cad61a7a59\n", + " name: Mechanical properties\n", + " inputs:\n", + " - id: id7edac774912e5\n", + " label: Tensile Strength R_m\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Megapascal\n", + " iri: http://qudt.org/vocab/unit/MegaPA\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_5d73661e_e710_4844_ab9b_a85b7e68576a\n", + " type: object_property\n", + " class_iri: https://w3id.org/steel/ProcessOntology/TensileStrength\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-03-14T13:59:00.319704'\n", + " updated_at: '2025-03-14T17:12:03.414629'\n", + "\n", + "ktype:\n", + " id: raw-material\n", + " name: Raw material\n", + " webform_schema_id: d74b4e3a-a514-4858-aba7-6dc9963eb1b7\n", + " webform_schema:\n", + " id: d74b4e3a-a514-4858-aba7-6dc9963eb1b7\n", + " name: Raw material\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\n", + " sections:\n", + " - id: id2297e7c70a55e\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id7568839cf603d\n", + " label: Name of the raw material\n", + " widget: Text\n", + " required: false\n", + " hint: 'Add the name of the raw material: e.g. iron ore'\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id82d4e002211d4\n", + " label: Masse\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Tonne\n", + " iri: http://qudt.org/vocab/unit/TONNE\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDefaultValue_Object\n", + " type: object_property\n", + " class_iri: https://w3id.org/emmo#EMMO_ed4af7ae_63a2_497e_bb88_2309619ea405\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-01-25T16:00:50.596497'\n", + " updated_at: '2025-02-26T23:11:39.497733'\n", + "\n", + "ktype:\n", + " id: project\n", + " name: Project\n", + " webform_schema_id: eb4fbf77-dc45-4b35-96e0-e12dc9858a88\n", + " webform_schema:\n", + " id: eb4fbf77-dc45-4b35-96e0-e12dc9858a88\n", + " name: Project\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/pmd/co/Project\n", + " sections:\n", + " - id: id4cdf930805703\n", + " name: Fundamental information\n", + " inputs:\n", + " - id: id1475863a6734b\n", + " label: Project short name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasProjectName_Object\n", + " type: object_property\n", + " class_iri: https://www.materials.fraunhofer.de/ontologies/BWMD_ontology/domain#BWMD_00394\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id0e2567e83ee82\n", + " label: Project name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasProjectName\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idb13d750ebf668\n", + " label: Organisation\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/org#organization\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: organization\n", + " - id: idda331a2c485b6\n", + " label: Project number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasProjectNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " placeholder: Please add the project number\n", + " - id: idad6f75e0569f8\n", + " label: Project members\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/agrelon#isMemberOf\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: expert\n", + " - id: idf533e1a7c76d8\n", + " label: Webpage of the project\n", + " widget: Text\n", + " required: false\n", + " hint: Please fill the http/https address of the webpage\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_ac852bf0_3251_4d6b_9e57_acbfcb5e7e08\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: id1b9da0f945fda\n", + " name: Project related data\n", + " inputs:\n", + " - id: ida9b609cef97dd\n", + " label: Dataset\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/related\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: dataset\n", + " - id: idf5a031991b475\n", + " label: Dataset catalog\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/related\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: dataset-catalog\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-01-25T11:37:41.262256'\n", + " updated_at: '2025-01-25T16:12:32.079754'\n", + "\n", + "ktype:\n", + " id: document\n", + " name: Document\n", + " webform_schema_id: 1a63e770-591c-4a2f-b1ec-76378692cb3a\n", + " webform_schema:\n", + " id: 1a63e770-591c-4a2f-b1ec-76378692cb3a\n", + " name: Document\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/pmd/co/Document\n", + " sections:\n", + " - id: id38dbc9d1053ad\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id81f0f0ebb5e3a\n", + " label: Publication title\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the title of the publication\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#publication\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id03034c5f9a788\n", + " label: Author or Authors\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the author in the form last name, first name, use semicolon\n", + " as separator\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#author\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: idd2286b953224c\n", + " label: Date of Publication\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#dateOfPublication\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: idcc2169d761d01\n", + " label: Keyword or Keywords\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the keyword, separated by semicolons\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/dcat#keyword\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id8889f147b9d65\n", + " label: Link to the source of the document\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/dcat#downloadURL\n", + " type: object_property\n", + " class_iri: http://purl.org/spar/datacite/ResourceIdentifier\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type:\n", + " - null\n", + " - id: id9141909089bd08\n", + " label: Type of Publication\n", + " widget: Select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Article\n", + " disabled: false\n", + " - key: option2\n", + " label: Book\n", + " disabled: false\n", + " - key: option3\n", + " label: Standard\n", + " disabled: false\n", + " - key: option4\n", + " label: Study\n", + " disabled: false\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasTypeOfPublication\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-01-26T10:25:35.097011'\n", + " updated_at: '2025-05-01T11:00:25.897314'\n", + "\n", + "ktype:\n", + " id: web-ressource\n", + " name: Web ressource\n", + " webform_schema_id: 2527c264-90d3-48bb-88ff-3837239024d7\n", + " webform_schema:\n", + " id: 2527c264-90d3-48bb-88ff-3837239024d7\n", + " name: Web ressource\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://schema.org/WebContent\n", + " sections:\n", + " - id: ida607b61c906a78\n", + " name: ''\n", + " inputs:\n", + " - id: idc605489032d46\n", + " label: Link to the web ressource\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the http or https address\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/ressourceUri\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-05-18T08:23:06.240726'\n", + " updated_at: '2025-05-18T08:23:06.240726'\n", + "\n", + "ktype:\n", + " id: measurement-process\n", + " name: Measurement process\n", + " webform_schema_id: 0097cafa-4a2d-4070-aebf-26111b047ca1\n", + " webform_schema:\n", + " id: 0097cafa-4a2d-4070-aebf-26111b047ca1\n", + " name: Measurement process\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/emmo/domain/characterisation-methodology/chameo#CharacterisationMeasurementProcess\n", + " sections:\n", + " - id: id034171a74270c\n", + " name: Basic information\n", + " inputs:\n", + " - id: idc229c5d518d05\n", + " label: Type of the Measurement Process\n", + " widget: Multi-select\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Tensile test\n", + " disabled: false\n", + " - key: option2\n", + " label: Shear test\n", + " disabled: false\n", + " - key: option3\n", + " label: Compression test\n", + " disabled: false\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idb7e4a5907b9b6\n", + " label: Operator\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasOperator\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: expert\n", + " hidden: false\n", + " - id: id3354e00cd7562\n", + " name: Specimen\n", + " inputs:\n", + " - id: id65d6ab413dadf\n", + " label: Specimen type\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: iddded49bc61745\n", + " label: 'Specimen '\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasInput\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: specimen\n", + " hidden: false\n", + " - id: id1f25c686b9c18\n", + " name: Measurement data\n", + " inputs:\n", + " - id: id6ead211a496bd\n", + " label: Dataset\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: annotation_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: dataset\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-03-07T17:57:43.973453'\n", + " updated_at: '2025-03-18T20:14:47.520674'\n", + "\n", + "ktype:\n", + " id: standard\n", + " name: Standard\n", + " webform_schema_id: 9fc0926e-d672-46cb-8939-011a1f6c0243\n", + " webform_schema:\n", + " id: 9fc0926e-d672-46cb-8939-011a1f6c0243\n", + " name: Standard\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/steel/ProcessOntology/Standard\n", + " sections:\n", + " - id: id42e0b41b4b41a8\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id0e43b10a181d\n", + " label: Title of the standard\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://purl.org/dc/terms/title\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-03-18T20:30:18.372742'\n", + " updated_at: '2025-03-18T20:57:36.405719'\n", + "\n", + "ktype:\n", + " id: external-data-source\n", + " name: Data source\n", + " webform_schema_id: b07f0caa-302f-4738-826b-072325c2df51\n", + " webform_schema:\n", + " id: b07f0caa-302f-4738-826b-072325c2df51\n", + " name: Data source\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - http://purl.org/spar/datacite/ResourceIdentifier\n", + " sections:\n", + " - id: ida97cb1490e7cd\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id1218ca74779d5\n", + " label: Name of the data source\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasDataSocurceName\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ide3c0cc34a59d\n", + " label: Link to the data source\n", + " widget: Text\n", + " required: false\n", + " hint: http/https-link to the data source\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/dcat#downloadURL\n", + " type: object_property\n", + " class_iri: http://purl.org/spar/datacite/ResourceIdentifier\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: ida14fe37d66557\n", + " name: Data source integration information\n", + " inputs:\n", + " - id: idcd41e2dd7d6c6\n", + " label: Semantic integration level\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/SemanticIntegrationLevelDataSource\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-01-26T11:57:31.456954'\n", + " updated_at: '2025-03-22T10:48:30.450920'\n", + "\n", + "ktype:\n", + " id: aluminium-alloy\n", + " name: Aluminium Alloy\n", + " webform_schema_id: dbb2ab1d-dc2a-47d9-aa4f-09475b0270f1\n", + " webform_schema:\n", + " id: dbb2ab1d-dc2a-47d9-aa4f-09475b0270f1\n", + " name: Aluminium Alloy\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/pmd/co/EngineeredMaterial\n", + " sections:\n", + " - id: id0c88760a11b16\n", + " name: Composition\n", + " inputs:\n", + " - id: id858e64bc18474\n", + " label: Aluminium mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasAluminiumMassFraction\n", + " type: object_property\n", + " class_iri: https://w3id.org/ORCHESTER/MassFraction\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idce87731554ab8\n", + " label: Copper mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasCopperMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idee3b37dc16d33\n", + " label: Silicium mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasSiliciumMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idad3a991ed01fe\n", + " label: Maganese mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasManganeseMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id0879e3ac279e\n", + " label: Nickel mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasNickelMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ida760eddfc0351\n", + " label: Iron mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasIronMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idf92a135418693\n", + " label: Zirconium mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasZirconiumMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ide0617f19494\n", + " label: Magnesium mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasMagnesiumMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id1d8fc04d2805e\n", + " label: Titanium mass fraction\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Percent\n", + " iri: http://qudt.org/vocab/unit/PERCENT\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasTitaniumMassFraction\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: id6c71facdbfa1b\n", + " name: Mechanical properties\n", + " inputs:\n", + " - id: id547326648372c\n", + " label: Yield stress\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Megapascal\n", + " iri: http://qudt.org/vocab/unit/MegaPA\n", + " namespace: http://qudt.org/vocab/unit\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasYieldStress_MegaPA\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: id5a29d7d611e43\n", + " name: State\n", + " inputs:\n", + " - id: id7679dc67608e6\n", + " label: State of the material\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasState\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-05T23:03:20.969312'\n", + " updated_at: '2025-02-07T08:34:49.108582'\n", + "\n", + "ktype:\n", + " id: carbonfootprint\n", + " name: Carbon Footprint\n", + " webform_schema_id: 93fafda2-4100-48ee-9b04-89e41c4b48e8\n", + " webform_schema:\n", + " id: 93fafda2-4100-48ee-9b04-89e41c4b48e8\n", + " name: Carbon Footprint\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/CarbonFootprint\n", + " sections:\n", + " - id: id55787634bf1a1\n", + " name: Product carbon footprint\n", + " inputs:\n", + " - id: idac53f04f4d77b\n", + " label: Impact assessment method or calculation method\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ImpactAssessmentMethod\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ida52ed70b6b1b4\n", + " label: CO2 equivalent climate change\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HasCO2eValue\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id96f4f0c818676\n", + " label: Reference value for calculation\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ReferenceValueForCalculation\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id459c57e31e284\n", + " label: Quantity of measure for calculation\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/QuantityOfMeasureForCalculation\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idddc69d02e5a2f\n", + " label: Life cycle phase\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/LifeCyclePhase\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id340968d815474\n", + " label: Publication date\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/PublicationDate\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idc926242f5d4d1\n", + " label: Expiration date\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ExpirationDate\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: iddb37353448a71\n", + " label: Explanatory statement\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ExplanatoryStatement\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: document\n", + " hidden: false\n", + " - id: id2dfc950f670c5\n", + " name: Transport carbon footprint\n", + " inputs: []\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-03-22T19:06:11.380008'\n", + " updated_at: '2025-03-23T07:44:35.790543'\n", + "\n", + "ktype:\n", + " id: address\n", + " name: Address\n", + " webform_schema_id: e736c6f5-165f-4f35-b971-d75ff12abfb2\n", + " webform_schema:\n", + " id: e736c6f5-165f-4f35-b971-d75ff12abfb2\n", + " name: Address\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/pmd/co/Address\n", + " sections:\n", + " - id: id6530fdb5d31f7\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id1fc65a963d12\n", + " label: Street\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/Street\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idc14c0177647d7\n", + " label: House number\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/HouseNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id7d2dfe0c5f82a\n", + " label: Zip code\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/ZipCode\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id2bf819fc77408\n", + " label: City\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/City\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idb6157ed71489\n", + " label: Country\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/Country\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-03-23T07:07:37.893283'\n", + " updated_at: '2025-03-23T07:14:15.200209'\n", + "\n", + "ktype:\n", + " id: manufacturing-process\n", + " name: Manufacturing Process\n", + " webform_schema_id: 4411fe95-b5a9-49a6-a7a2-5d231469bd64\n", + " webform_schema:\n", + " id: 4411fe95-b5a9-49a6-a7a2-5d231469bd64\n", + " name: Manufacturing process\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " sections:\n", + " - id: idc31851effc76f\n", + " name: ''\n", + " inputs:\n", + " - id: id9e62c8f05fa6e8\n", + " label: Process name\n", + " widget: Text\n", + " required: false\n", + " hint: Name of the manufacturing process (e.g., Hot Rolling)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id5ff5607f7d43f\n", + " label: Process Description\n", + " widget: Text\n", + " required: false\n", + " hint: Brief description of the process\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id4804cada693ad8\n", + " label: Process duration\n", + " widget: Number\n", + " required: false\n", + " hint: Typical duration of the process\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: min\n", + " namespace: None\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTime_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id2b41376167e7f\n", + " label: Temperature\n", + " widget: Number\n", + " required: false\n", + " hint: Operating temperature (if applicable)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: DegC\n", + " namespace: None\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id700c62fd038a58\n", + " label: Cost\n", + " widget: Number\n", + " required: false\n", + " hint: Estimated cost of the process\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: Euro/min\n", + " namespace: None\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-10-23T13:50:27.891897'\n", + " updated_at: '2026-05-03T14:54:46.818981'\n", + "\n", + "ktype:\n", + " id: expert\n", + " name: Expert\n", + " webform_schema_id: 3c4d676d-03a7-40d6-8c1b-4a0b9b56a5d9\n", + " webform_schema:\n", + " id: 3c4d676d-03a7-40d6-8c1b-4a0b9b56a5d9\n", + " name: expert\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: true\n", + " class_mapping:\n", + " - https://d-nb.info/standards/elementset/gnd#Person\n", + " sections:\n", + " - id: id01d808779f18d\n", + " name: General information about the Expert\n", + " inputs:\n", + " - id: id73014ceccdf35\n", + " label: First Name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/firstName\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: ide3e3058da9784\n", + " label: Last name\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/lastName\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id220e33fbd6ce1\n", + " label: Employer\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/agrelon#hasEmployer\n", + " type: object_property\n", + " class_iri: https://w3id.org/emmo#EMMO_c0f72631_d7c2_434c_9c26_5c44123df682\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: organization\n", + " - id: id4360d4bc4ab02\n", + " label: Department\n", + " widget: Knowledge item\n", + " required: false\n", + " hint: 'Select the '\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/org#memberOf\n", + " type: property\n", + " class_iri: http://www.w3.org/ns/org#OrganizationalUnit\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: organization\n", + " - id: id693ce596955fb\n", + " label: Experts you know\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/knows\n", + " type: object_property\n", + " class_iri: https://d-nb.info/standards/elementset/gnd#Person\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: expert\n", + " hidden: false\n", + " - id: id9ad5da56ad5dd\n", + " name: Expertise\n", + " inputs:\n", + " - id: idc90df1ddeb336\n", + " label: Expertise in materials\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#topic\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: engineered-material\n", + " - id: id7aba01bb91426\n", + " label: Expertise in material modelling\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#topic\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: material-model\n", + " - id: id47391ebd4d64\n", + " label: Expertise in measurement devices\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasMeasurementDeviceName\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: true\n", + " knowledge_type: measurement-device\n", + " hidden: false\n", + " - id: id69dc30cd5bd52\n", + " name: Education\n", + " inputs:\n", + " - id: id2fe41d8056068\n", + " label: Education or Field of Study\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://d-nb.info/standards/elementset/gnd#fieldOfStudy\n", + " type: object_property\n", + " class_iri: https://d-nb.info/standards/elementset/gnd#Gods\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-07-28T18:48:21.024154'\n", + " updated_at: '2026-05-11T12:12:16.458478'\n", + "\n", + "ktype:\n", + " id: semi-finished-product\n", + " name: Semi-finished product\n", + " webform_schema_id: 224297da-9a8f-4db7-af8f-37dcff8a4bbd\n", + " webform_schema:\n", + " id: 224297da-9a8f-4db7-af8f-37dcff8a4bbd\n", + " name: Semi-finished product\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " sections:\n", + " - id: id2f5555b5b6faf\n", + " name: ''\n", + " inputs:\n", + " - id: ida071d231324aa8\n", + " label: Product name\n", + " widget: Text\n", + " required: false\n", + " hint: Name of the semi-finished product (e.g. Slab)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasIdentifier_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id8c02df4e44758\n", + " label: Product Description\n", + " widget: Textarea\n", + " required: false\n", + " hint: Brief description of the product\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: idf272d26c87f7c8\n", + " label: Width\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: mm\n", + " namespace: None\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasWidth_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: idcc11a8330cfb88\n", + " label: Length\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: mm\n", + " namespace: None\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTotalLength_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: idfda8888c1ce61\n", + " label: Thickness\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: mm\n", + " namespace: None\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasOriginalThickness_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: id1959ef37f8cb2\n", + " label: Weigth\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " iri: None\n", + " symbol: g\n", + " namespace: None\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: idd62839912aa2c\n", + " label: Surface finish\n", + " widget: Select\n", + " required: false\n", + " value: ''\n", + " hint: Type of surface finish (e.g., rough, smooth)\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Rough\n", + " disabled: false\n", + " - key: option2\n", + " label: Smooth\n", + " disabled: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " - id: idc53d9915e39df8\n", + " label: Material state\n", + " widget: Select\n", + " required: false\n", + " value: ''\n", + " hidden: false\n", + " ignore: false\n", + " select_options:\n", + " - key: option1\n", + " label: Raw\n", + " disabled: false\n", + " - key: option2\n", + " label: Rolled\n", + " disabled: false\n", + " - key: option3\n", + " label: Cut\n", + " disabled: false\n", + " - key: option4\n", + " label: Heat Treated\n", + " disabled: false\n", + " - key: option5\n", + " label: Annealed\n", + " disabled: false\n", + " - key: option6\n", + " label: Quenched\n", + " disabled: false\n", + " - key: option7\n", + " label: Tempered\n", + " disabled: false\n", + " - key: option8\n", + " label: Assembled\n", + " disabled: false\n", + " - key: option9\n", + " label: Machined\n", + " disabled: false\n", + " - key: option10\n", + " label: Fabricated\n", + " disabled: false\n", + " - key: option11\n", + " label: Recycled\n", + " disabled: false\n", + " - key: option12\n", + " label: Blanked\n", + " disabled: false\n", + " - key: option13\n", + " label: Finished\n", + " disabled: false\n", + " multiple_selection: false\n", + " range_options:\n", + " min: 0\n", + " max: 0\n", + " step: 0\n", + " range: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-10-23T13:54:07.583405'\n", + " updated_at: '2024-11-15T11:30:49.341861'\n", + "\n", + "ktype:\n", + " id: raw-material-profile\n", + " name: Raw material profile\n", + " webform_schema_id: 6e116711-2aff-4efb-960b-7a2c6540985e\n", + " webform_schema:\n", + " id: 6e116711-2aff-4efb-960b-7a2c6540985e\n", + " name: Raw material profile\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\n", + " sections:\n", + " - id: idecd132593127b\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: idd5569135a88cb\n", + " label: Raw material (name of the chemical element)\n", + " widget: Knowledge item\n", + " required: false\n", + " hint: Please add the name of the chemical element\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_79c0edfa_06f9_5149_b754_28c589035b8a\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: chemical-element\n", + " - id: ida85038c9ab6d3\n", + " label: RMIS source\n", + " widget: Text\n", + " required: false\n", + " hint: https-link to the RMIS Source\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/emmo#EMMO_ac852bf0_3251_4d6b_9e57_acbfcb5e7e08\n", + " type: property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-01-26T11:16:03.599447'\n", + " updated_at: '2025-01-26T11:31:06.709956'\n", + "\n", + "ktype:\n", + " id: tensile-test\n", + " name: Tensile Test\n", + " created_at: '2026-05-03T14:40:51.935637'\n", + " updated_at: '2026-05-03T14:40:51.935637'\n", + "\n", + "ktype:\n", + " id: organization\n", + " name: Organization\n", + " webform_schema_id: 9a110d95-49fb-4c8f-8565-52a4848e3361\n", + " webform_schema:\n", + " id: 9a110d95-49fb-4c8f-8565-52a4848e3361\n", + " name: organization\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/emmo#EMMO_c0f72631_d7c2_434c_9c26_5c44123df682\n", + " sections:\n", + " - id: id21866f42d5176\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id4d61120f8538b\n", + " label: Name of the Organisation\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id2af8ad6c4bb5a\n", + " label: Sub Organisation of\n", + " widget: Knowledge item\n", + " required: false\n", + " hint: Please select the upper organisation\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/org#subOrganizationOf\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: organization\n", + " - id: id5880a22a80ecf\n", + " label: URL of the webpage\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/dcat#accessURL\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id3e32c796f00c7\n", + " label: City\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/org#location\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id5597c3fd52603\n", + " label: Address\n", + " widget: Knowledge item\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: http://www.w3.org/ns/org#location\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: address\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2024-07-28T18:48:21.024154'\n", + " updated_at: '2026-05-03T14:50:20.049632'\n", + "\n", + "ktype:\n", + " id: material\n", + " name: Material\n", + " created_at: '2026-05-03T14:50:25.990175'\n", + " updated_at: '2026-05-03T14:50:25.990175'\n", + "\n", + "ktype:\n", + " id: dasda\n", + " name: dasda\n", + " created_at: '2026-05-03T15:18:30.461044'\n", + " updated_at: '2026-05-03T15:18:30.461044'\n", + "\n", + "ktype:\n", + " id: process\n", + " name: Process\n", + " created_at: '2026-05-03T15:36:18.563875'\n", + " updated_at: '2026-05-03T15:36:18.563875'\n", + "\n", + "ktype:\n", + " id: chemical-composition\n", + " name: Chemical Composition\n", + " created_at: '2026-05-03T16:34:37.231511'\n", + " updated_at: '2026-05-03T16:34:37.231511'\n", + "\n", + "ktype:\n", + " id: measurement-device\n", + " name: Measurement Device\n", + " webform_schema_id: ab1ab1d5-10c6-4096-a99b-3e6fb43478ea\n", + " webform_schema:\n", + " id: ab1ab1d5-10c6-4096-a99b-3e6fb43478ea\n", + " name: Measurement device\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/MeasurementDevice\n", + " sections:\n", + " - id: id23f26e1aec6e\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id4771ba047219e\n", + " label: Name of measurement device\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the name of the measurement device\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasMeasurementDeviceName\n", + " type: data_property\n", + " class_iri: https://w3id.org/ORCHESTER/measurement-device/MeasurementDeviceName\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id3d095d631a156\n", + " label: Manufacturer\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the name of the manufacturer\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasManufacturer\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id4584da4ce4bc9\n", + " label: Measurement device type\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the measurement type of the device, e.g. microscope\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/MeasurementDeviceType\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id071732bc59fed\n", + " label: Serial number\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the serial number\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasSerialNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id7550e1c6439dd\n", + " label: Responsible organization for the device\n", + " widget: Knowledge item\n", + " required: false\n", + " hint: Please add the responsible department of the organisation\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasResponsibleOrganizationUnit\n", + " type: object_property\n", + " class_iri: https://w3id.org/emmo#EMMO_c0f72631_d7c2_434c_9c26_5c44123df682\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: organization\n", + " - id: id2095e14118a78\n", + " label: Room number\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the room number of the device\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasRoomNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idcce5f8dfb7778\n", + " label: Inventory number\n", + " widget: Text\n", + " required: false\n", + " hint: please add the sigma inventory number\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasInventoryNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id3998befe78c2c\n", + " label: Measurement range\n", + " widget: Textarea\n", + " required: false\n", + " hint: Please add relevant information about the measurement range\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasMeasurementRange\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-17T11:22:02.248650'\n", + " updated_at: '2026-05-25T15:17:12.862130'\n", + "\n", + "ktype:\n", + " id: material-card\n", + " name: Material Card\n", + " created_at: '2026-05-25T15:36:20.811768'\n", + " updated_at: '2026-05-25T15:36:20.811768'\n", + "\n", + "ktype:\n", + " id: person\n", + " name: Person\n", + " created_at: '2026-05-25T16:35:22.337537'\n", + " updated_at: '2026-05-25T16:35:22.337537'\n", + "\n", + "ktype:\n", + " id: creep-specimen\n", + " name: Creep Specimen\n", + " webform_schema_id: 195680a9-25d0-4630-b160-f412a31725d5\n", + " webform_schema:\n", + " id: 195680a9-25d0-4630-b160-f412a31725d5\n", + " name: Creep specimen\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping: http://purl.obolibrary.org/obo/OBI_0100051\n", + " sections:\n", + " - id: section-creep-gauge\n", + " name: Gauge Section\n", + " inputs:\n", + " - id: input-creep-gauge-length\n", + " label: Gauge length\n", + " widget: Number\n", + " required: false\n", + " hint: Length of the parallel gauge section (L0)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasGaugeLengthLo_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-creep-gauge-diameter\n", + " label: Gauge diameter\n", + " widget: Number\n", + " required: false\n", + " hint: Diameter of the cylindrical gauge section (for round specimens)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDiameter_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " - id: section-creep-grip\n", + " name: Grip Ends\n", + " inputs:\n", + " - id: input-creep-grip-type\n", + " label: Grip type\n", + " widget: Select\n", + " required: false\n", + " hint: How the specimen is held in the testing machine\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleType_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-creep-thread-spec\n", + " label: Thread specification\n", + " widget: Text\n", + " required: false\n", + " hint: \"e.g. M10 \\xD7 1.5, 3/8-24 UNF\"\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleInfos_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2026-06-04T16:45:11.535394'\n", + " created_at: '2025-04-10T17:59:37.038081'\n", + " updated_at: '2026-06-03T14:30:12.659529'\n", + "\n", + "ktype:\n", + " id: flat-specimen\n", + " name: Flat Specimen\n", + " webform_schema_id: a6bad5c1-6c0c-4d7d-81a9-342df6c0bc53\n", + " webform_schema:\n", + " id: a6bad5c1-6c0c-4d7d-81a9-342df6c0bc53\n", + " name: flat-specimen_custom_properties\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping: https://w3id.org/pmd/co/Specimen\n", + " sections:\n", + " - id: section-flat-gauge\n", + " name: Gauge Section\n", + " inputs:\n", + " - id: input-flat-gauge-length\n", + " label: Gauge length\n", + " widget: Number\n", + " required: false\n", + " hint: Parallel section length over which strain is measured (L0)\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasGaugeLengthLo_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-flat-gauge-width\n", + " label: Gauge width\n", + " widget: Number\n", + " required: false\n", + " hint: Width within the parallel gauge section\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasParallelSectionWidth_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-flat-shoulder-radius\n", + " label: Shoulder radius\n", + " widget: Number\n", + " required: false\n", + " hint: Transition radius between gauge section and grip area\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasRadius_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2026-06-03T18:19:43.815570'\n", + " updated_at: '2026-06-03T18:37:53.450387'\n", + " created_at: '2026-06-03T14:32:17.816061'\n", + " updated_at: '2026-06-03T18:19:43.815570'\n", + "\n", + "ktype:\n", + " id: metal-sheet-batch\n", + " name: Metal Sheet Batch\n", + " created_at: '2026-06-04T13:36:01.424615'\n", + " updated_at: '2026-06-04T13:36:01.424615'\n", + "\n", + "ktype:\n", + " id: specimen-batch\n", + " name: Specimen Batch\n", + " created_at: '2026-06-04T13:56:07.018955'\n", + " updated_at: '2026-06-04T13:56:07.018955'\n", + "\n", + "ktype:\n", + " id: batch\n", + " name: Batch\n", + " webform_schema_id: ff2017ab-4371-40dd-ae54-6cc806e660b2\n", + " webform_schema:\n", + " id: ff2017ab-4371-40dd-ae54-6cc806e660b2\n", + " name: batch_custom_properties\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping: http://www.w3.org/ns/prov#Collection\n", + " sections:\n", + " - id: section-batch-info\n", + " name: Batch Information\n", + " inputs:\n", + " - id: input-batch-id\n", + " label: Batch ID\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://schema.org/identifier\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-batch-date\n", + " label: Production / receipt date\n", + " widget: Date\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://schema.org/dateCreated\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-batch-quantity\n", + " label: Quantity\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://schema.org/numberOfItems\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2026-06-04T16:12:01.564921'\n", + " updated_at: '2026-06-04T16:34:06.838525'\n", + " created_at: '2026-06-03T15:03:46.122464'\n", + " updated_at: '2026-06-04T16:12:01.564921'\n", + "\n", + "ktype:\n", + " id: metal-sheet\n", + " name: Metal Sheet\n", + " webform_schema_id: ca38d31b-d35a-4880-804e-1c3b138b159e\n", + " webform_schema:\n", + " id: ca38d31b-d35a-4880-804e-1c3b138b159e\n", + " name: metal-sheet_custom_properties\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping: []\n", + " sections:\n", + " - id: section-sheet-dimensions\n", + " name: Dimensions\n", + " inputs:\n", + " - id: input-sheet-thickness\n", + " label: Thickness\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceThickness_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-sheet-width\n", + " label: Width\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasWidth_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-sheet-length\n", + " label: Length\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " measurement_unit:\n", + " label: Millimetre\n", + " iri: http://qudt.org/vocab/unit/MilliM\n", + " symbol: mm\n", + " namespace: qudt\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceLength_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-sheet-surface\n", + " label: Surface condition\n", + " widget: Text\n", + " required: false\n", + " hint: e.g. hot-rolled, cold-rolled, annealed, pickled\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleInfos_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2026-06-04T17:01:11.181898'\n", + " updated_at: '2026-06-04T17:01:11.181898'\n", + " created_at: '2026-06-04T17:01:11.181898'\n", + " updated_at: '2026-06-04T17:01:11.181898'\n", + "\n" + ] + } + ], + "source": [ + "for ktype in dsms.ktypes:\n", + " print(ktype)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Note on deprecated fields\n", + "\n", + "Some fields visible in KItem outputs above (`authors`, `rdf_exists`, `user_groups`) are deprecated in v5.0.0 and are no longer populated by the server. Use `access_properties` (a `KItemAccessProperties` object with `user_access` and `group_access` lists) for access control. These deprecated fields remain in the model for backward compatibility only.\n", + "\n", + "Upcoming tutorials cover `access_properties`, `schema_data` (semantic schema instance data), and `contexts` (grouping KItems into context containers)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You have completed the introduction tutorial. Continue with [2. Creation](2_creation.ipynb) to learn how to create new KItems." ] } ], @@ -564,7 +3915,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/2_creation.ipynb b/docs/dsms_sdk/tutorials/2_creation.ipynb index b7ac841..7711a2b 100644 --- a/docs/dsms_sdk/tutorials/2_creation.ipynb +++ b/docs/dsms_sdk/tutorials/2_creation.ipynb @@ -6,7 +6,7 @@ "source": [ "# 2. Create KItems with the SDK\n", "\n", - "In this tutorial we see how to create new Kitems." + "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 [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established 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." ] @@ -22,7 +22,14 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:01.020763Z", + "iopub.status.busy": "2026-06-07T20:52:01.020541Z", + "iopub.status.idle": "2026-06-07T20:52:01.687641Z", + "shell.execute_reply": "2026-06-07T20:52:01.686445Z" + } + }, "outputs": [], "source": [ "from dsms import DSMS, KItem" @@ -38,10 +45,18 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:01.690742Z", + "iopub.status.busy": "2026-06-07T20:52:01.690406Z", + "iopub.status.idle": "2026-06-07T20:52:02.203284Z", + "shell.execute_reply": "2026-06-07T20:52:02.202115Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" ] }, { @@ -49,27 +64,33 @@ "metadata": {}, "source": [ "\n", - "### 2.2: Create KItems" + "### 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", - "#" + "We can make new KItems by simple class-initiation. (Do not pass an existing KItem as input, as this will raise an error.)" ] }, { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:02.206918Z", + "iopub.status.busy": "2026-06-07T20:52:02.206739Z", + "iopub.status.idle": "2026-06-07T20:52:02.438543Z", + "shell.execute_reply": "2026-06-07T20:52:02.437328Z" + } + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/app/dsms/knowledge/kitem.py:406: UserWarning: A flat dictionary was provided for custom properties.\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:430: UserWarning: A flat dictionary was provided for custom properties.\n", " Will be transformed into `KItemCustomPropertiesModel`.\n", " warnings.warn(\n" ] @@ -83,31 +104,21 @@ " custom_properties:\n", " content:\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", " entries:\n", - " - id: id6c76bbffe7ca78\n", + " - id: input-specimen-width\n", " type: Number\n", " label: Width\n", " value: 0.5\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", + " - id: input-specimen-length\n", + " type: Number\n", " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " value: 0.15\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false" ] @@ -123,7 +134,7 @@ " ktype_id=dsms.ktypes.Specimen,\n", " custom_properties = {\n", " \"Width\": 0.5,\n", - " \"Length\": [0.1, 0.2],\n", + " \"Length\": 0.15,\n", " }\n", ")\n", "\n", @@ -134,18 +145,25 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Remember: changes are only syncronized with the DSMS when you call the `commit`-method:" + "Remember: changes are only synchronized with the DSMS when you call the `commit`-method:" ] }, { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:02.444718Z", + "iopub.status.busy": "2026-06-07T20:52:02.444547Z", + "iopub.status.idle": "2026-06-07T20:52:04.586941Z", + "shell.execute_reply": "2026-06-07T20:52:04.586003Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "'https://bue.materials-data.space/knowledge/specimen/specimen123-5cb3a95e'" + "'https://nash.materials-data.space/knowledge/specimen/specimen123-227d1678'" ] }, "execution_count": 4, @@ -169,61 +187,62 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.588944Z", + "iopub.status.busy": "2026-06-07T20:52:04.588786Z", + "iopub.status.idle": "2026-06-07T20:52:04.594447Z", + "shell.execute_reply": "2026-06-07T20:52:04.593683Z" + } + }, "outputs": [ { "data": { "text/plain": [ "kitem:\n", - " id: 5cb3a95e-4aac-434d-81ba-7c242fd245aa\n", + " id: 227d1678-aa56-4598-9082-540c629fd8a8\n", " name: Specimen123\n", " ktype_id: specimen\n", - " slug: specimen123-5cb3a95e\n", + " slug: specimen123-227d1678\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations: []\n", - " attachments:\n", - " - name: subgraph.ttl\n", + " attachments: []\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " authors: []\n", " contacts: []\n", - " created_at: 2025-08-13 14:29:01.826691\n", - " updated_at: 2025-08-13 14:29:01.826691\n", + " created_at: 2026-06-07 20:52:02.878207\n", + " updated_at: 2026-06-07 20:52:02.878207\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", " entries:\n", - " - id: id6c76bbffe7ca78\n", + " - id: input-specimen-width\n", " type: Number\n", " label: Width\n", " value: 0.5\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", + " - id: input-specimen-length\n", + " type: Number\n", " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " value: 0.15\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " rdf_exists: true\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []" ] }, @@ -246,7 +265,14 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.596101Z", + "iopub.status.busy": "2026-06-07T20:52:04.595959Z", + "iopub.status.idle": "2026-06-07T20:52:04.599363Z", + "shell.execute_reply": "2026-06-07T20:52:04.598541Z" + } + }, "outputs": [ { "data": { @@ -267,18 +293,25 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As well as the id of the kitem we can do it as follows:" + "As well as the id of the KItem we can do it as follows:" ] }, { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.600769Z", + "iopub.status.busy": "2026-06-07T20:52:04.600633Z", + "iopub.status.idle": "2026-06-07T20:52:04.603939Z", + "shell.execute_reply": "2026-06-07T20:52:04.603141Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "UUID('5cb3a95e-4aac-434d-81ba-7c242fd245aa')" + "UUID('227d1678-aa56-4598-9082-540c629fd8a8')" ] }, "execution_count": 7, @@ -300,7 +333,14 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.605534Z", + "iopub.status.busy": "2026-06-07T20:52:04.605391Z", + "iopub.status.idle": "2026-06-07T20:52:04.612764Z", + "shell.execute_reply": "2026-06-07T20:52:04.611929Z" + } + }, "outputs": [ { "data": { @@ -308,72 +348,108 @@ "ktype:\n", " id: specimen\n", " name: Specimen\n", - " webform_schema_id: 21164fb6-cc45-4e08-8f8b-467a749df54b\n", + " webform_schema_id: 7199e339-2512-4aed-8b99-af495c0349d3\n", " webform_schema:\n", - " id: 21164fb6-cc45-4e08-8f8b-467a749df54b\n", + " id: 7199e339-2512-4aed-8b99-af495c0349d3\n", " name: Specimen\n", " spec:\n", " semantics_enabled: true\n", " sections_enabled: false\n", - " class_mapping:\n", - " - https://w3id.org/pmd/co/Specimen\n", + " class_mapping: http://purl.obolibrary.org/obo/OBI_0100051\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", " inputs:\n", - " - id: id6c76bbffe7ca78\n", + " - id: input-specimen-type\n", + " label: Specimen type\n", + " widget: Text\n", + " required: false\n", + " hint: e.g. flat, round, notched\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleType_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-geometry\n", + " label: Specimen geometry\n", + " widget: Text\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceGeometry_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-width\n", " label: Width\n", " widget: Number\n", " required: false\n", " hidden: false\n", " ignore: false\n", " select_options: []\n", - " measurement_unit:\n", - " label: Millimetre\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " namespace: http://qudt.org/vocab/unit\n", " relation_mapping:\n", - " iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " type: object_property\n", - " class_iri: https://w3id.org/emmo#EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0\n", + " iri: https://w3id.org/steel/ProcessOntology/hasWidth_Object\n", + " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", - " knowledge_type:\n", - " - null\n", - " range_options:\n", - " min: 0\n", - " max: 1\n", - " step: 0.1\n", - " range: false\n", - " - id: id717d07130a7618\n", + " - id: input-specimen-length\n", " label: Length\n", - " widget: Slider\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceLength_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-thickness\n", + " label: Thickness\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasTestPieceThickness_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-diameter\n", + " label: Diameter\n", + " widget: Number\n", + " required: false\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDiameter_Object\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: input-specimen-description\n", + " label: Additional description\n", + " widget: Textarea\n", " required: false\n", " hidden: false\n", " ignore: false\n", " select_options: []\n", - " measurement_unit:\n", - " label: Millimetre\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " namespace: http://qudt.org/vocab/unit\n", " relation_mapping:\n", - " iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " type: object_property\n", - " class_iri: https://w3id.org/emmo#EMMO_cd2cd0de_e0cc_4ef1_b27e_2e88db027bac\n", - " relation_mapping_extra:\n", - " iri: https://w3id.org/emmo#EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f\n", - " type: object_property\n", - " class_iri: https://w3id.org/emmo#EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleInfos_Object\n", + " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", - " range_options:\n", - " min: 0\n", - " max: 1\n", - " step: 0.1\n", - " range: true\n", " hidden: false\n", - " created_at: '2025-07-21T09:24:48.597715'\n", - " updated_at: '2025-07-21T13:56:38.225444'\n", - " created_at: '2025-07-21T09:20:12.746430'\n", - " updated_at: '2025-07-21T09:24:58.495720'" + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2026-06-04T16:44:35.090388'\n", + " created_at: '2024-10-23T13:59:30.448842'\n", + " updated_at: '2024-11-15T11:28:54.289602'" ] }, "execution_count": 8, @@ -395,7 +471,14 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.614193Z", + "iopub.status.busy": "2026-06-07T20:52:04.614056Z", + "iopub.status.idle": "2026-06-07T20:52:04.617401Z", + "shell.execute_reply": "2026-06-07T20:52:04.616627Z" + } + }, "outputs": [ { "data": { @@ -422,41 +505,30 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.618989Z", + "iopub.status.busy": "2026-06-07T20:52:04.618855Z", + "iopub.status.idle": "2026-06-07T20:52:04.707770Z", + "shell.execute_reply": "2026-06-07T20:52:04.706752Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "@prefix ns1: .\n", - "@prefix ns2: .\n", - "@prefix rdfs: .\n", - "@prefix xsd: .\n", - "\n", - " a ;\n", - " rdfs:label \"Specimen123\"^^xsd:string ;\n", - " ns1:EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f ,\n", - " ,\n", - " .\n", - "\n", - " a ns1:EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0 ;\n", - " ns2:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI ;\n", - " ns2:value \"0.5\"^^xsd:float .\n", - "\n", - " a ns1:EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0 ;\n", - " ns2:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI ;\n", - " ns2:value \"0.2\"^^xsd:float .\n", - "\n", - " a ns1:EMMO_cd2cd0de_e0cc_4ef1_b27e_2e88db027bac ;\n", - " ns2:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI ;\n", - " ns2:value \"0.1\"^^xsd:float .\n", - "\n", - "\n" + "Note: RDF subgraph is generated asynchronously.\n", + "It may not be available immediately after creation.\n" ] } ], "source": [ - "print(item.subgraph.serialize())" + "try:\n", + " print(item.subgraph.serialize())\n", + "except ValueError as e:\n", + " print(f\"Note: RDF subgraph is generated asynchronously.\")\n", + " print(f\"It may not be available immediately after creation.\")" ] }, { @@ -469,41 +541,57 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.709914Z", + "iopub.status.busy": "2026-06-07T20:52:04.709761Z", + "iopub.status.idle": "2026-06-07T20:52:04.791436Z", + "shell.execute_reply": "2026-06-07T20:52:04.790452Z" + } + }, "outputs": [ { - "data": { - "text/plain": [ - "0.0005" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Unit conversion not available: Property `Width` does not own any\n", + " unit with respect to the semantics applied.\n" + ] } ], "source": [ - "item.custom_properties.Width.convert_to(\"m\")" + "try:\n", + " item.custom_properties.Width.convert_to(\"m\")\n", + "except ValueError as e:\n", + " print(f\"Unit conversion not available: {e}\")" ] }, { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.793830Z", + "iopub.status.busy": "2026-06-07T20:52:04.793678Z", + "iopub.status.idle": "2026-06-07T20:52:04.873432Z", + "shell.execute_reply": "2026-06-07T20:52:04.872431Z" + } + }, "outputs": [ { - "data": { - "text/plain": [ - "[0.0001, 0.0002]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Unit conversion not available: Property `Length` does not own any\n", + " unit with respect to the semantics applied.\n" + ] } ], "source": [ - "item.custom_properties.Length.convert_to(\"m\")" + "try:\n", + " item.custom_properties.Length.convert_to(\"m\")\n", + "except ValueError as e:\n", + " print(f\"Unit conversion not available: {e}\")" ] }, { @@ -516,12 +604,19 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.875471Z", + "iopub.status.busy": "2026-06-07T20:52:04.875312Z", + "iopub.status.idle": "2026-06-07T20:52:04.879181Z", + "shell.execute_reply": "2026-06-07T20:52:04.878411Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "{'Width': 0.5, 'Length': [0.1, 0.2]}" + "{'Width': 0.5, 'Length': 0.15}" ] }, "execution_count": 13, @@ -533,12 +628,57 @@ "item.custom_properties.model_dump(flat=True)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.x. Setting access properties\n", + "\n", + "Access control is defined via `access_properties`, which assigns roles to specific users and groups. The available roles are `MEMBER` (read only), `CONTRIBUTOR` (read and update), and `OWNER` (read, update, delete, manage)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:04.880881Z", + "iopub.status.busy": "2026-06-07T20:52:04.880744Z", + "iopub.status.idle": "2026-06-07T20:52:05.038642Z", + "shell.execute_reply": "2026-06-07T20:52:05.037445Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/core/dsms.py:222: UserWarning: Nothing to commit. No changes have been made to the DSMS instance.If you would like to add&/delete KItems, KTypes or AppConfigs,please use: `dsms.add(my_object)` or dsms.delete(my_object)`before running `dsms.commit()`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from dsms.knowledge.properties.access import KItemAccessProperties, Role\n", + "\n", + "# Look up the current user to demonstrate role assignment\n", + "uname = dsms.config.username\n", + "if hasattr(uname, \"get_secret_value\"):\n", + " uname = uname.get_secret_value()\n", + "current_user = dsms.users.by_username.get(uname)\n", + "\n", + "item.access_properties = KItemAccessProperties(\n", + " user_access=[{\"user_id\": current_user.id, \"role\": Role.OWNER}],\n", + ")\n", + "dsms.commit()" + ] + }, { "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", + "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", @@ -562,7 +702,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/3_updating.ipynb b/docs/dsms_sdk/tutorials/3_updating.ipynb new file mode 100644 index 0000000..2490cde --- /dev/null +++ b/docs/dsms_sdk/tutorials/3_updating.ipynb @@ -0,0 +1,510 @@ +{ + "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", + "\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established 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": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:08.957468Z", + "iopub.status.busy": "2026-06-07T20:52:08.957268Z", + "iopub.status.idle": "2026-06-07T20:52:09.723609Z", + "shell.execute_reply": "2026-06-07T20:52:09.722427Z" + } + }, + "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": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:09.726448Z", + "iopub.status.busy": "2026-06-07T20:52:09.726044Z", + "iopub.status.idle": "2026-06-07T20:52:10.252776Z", + "shell.execute_reply": "2026-06-07T20:52:10.251529Z" + } + }, + "outputs": [], + "source": [ + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let us get the KItem we created in the [2nd tutorial: Creation of KItems](2_creation.ipynb)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:10.256562Z", + "iopub.status.busy": "2026-06-07T20:52:10.256369Z", + "iopub.status.idle": "2026-06-07T20:52:12.724457Z", + "shell.execute_reply": "2026-06-07T20:52:12.723321Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:430: UserWarning: A flat dictionary was provided for custom properties.\n", + " Will be transformed into `KItemCustomPropertiesModel`.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "kitem:\n", + " id: 1d2ba175-d61f-4e31-85f6-0bc6830b5cb8\n", + " name: Specimen123\n", + " ktype_id: specimen\n", + " slug: specimen123-1d2ba175\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " authors: []\n", + " contacts: []\n", + " created_at: 2026-06-07 20:52:10.917226\n", + " updated_at: 2026-06-07 20:52:10.917226\n", + " external_links: []\n", + " apps: []\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", + " entries:\n", + " - id: input-specimen-width\n", + " type: Number\n", + " label: Width\n", + " value: 0.5\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " - id: input-specimen-length\n", + " type: Number\n", + " label: Length\n", + " value: 0.15\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item = KItem(\n", + " name=\"Specimen123\",\n", + " ktype_id=dsms.ktypes.Specimen,\n", + " custom_properties={\"Width\": 0.5, \"Length\": 0.15},\n", + ")\n", + "dsms.add(item)\n", + "dsms.commit()\n", + "item" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:12.726584Z", + "iopub.status.busy": "2026-06-07T20:52:12.726423Z", + "iopub.status.idle": "2026-06-07T20:52:12.731697Z", + "shell.execute_reply": "2026-06-07T20:52:12.730855Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "kitem:\n", + " id: 1d2ba175-d61f-4e31-85f6-0bc6830b5cb8\n", + " name: Specimen123\n", + " ktype_id: specimen\n", + " slug: specimen123-1d2ba175\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " authors: []\n", + " contacts: []\n", + " created_at: 2026-06-07 20:52:10.917226\n", + " updated_at: 2026-06-07 20:52:10.917226\n", + " external_links: []\n", + " apps: []\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", + " entries:\n", + " - id: input-specimen-width\n", + " type: Number\n", + " label: Width\n", + " value: 0.5\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " - id: input-specimen-length\n", + " type: Number\n", + " label: Length\n", + " value: 0.15\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, + { + "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)), `list` accumulation (`+=` or `-=`), e.g. for the `annotations`, `attachments`, `external_link`, etc. \n", + "\n", + "**NOTE**: using `append` or `extend` will not validate the pydantic model of the respective fields and hence will cause an error during committing. Hence, always use `+=`, `=` or `-=`.\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": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:12.733434Z", + "iopub.status.busy": "2026-06-07T20:52:12.733251Z", + "iopub.status.idle": "2026-06-07T20:52:12.736005Z", + "shell.execute_reply": "2026-06-07T20:52:12.735628Z" + } + }, + "outputs": [], + "source": [ + "item.name = \"Specimen-123\"\n", + "item.custom_properties.Width = 1\n", + "item.attachments += [\"testfile.txt\"]\n", + "item.annotations += [\"https://w3id.org/pmd/co/Specimen\"]\n", + "item.external_links += [\n", + " {\"url\": \"http://specimens.org\", \"label\": \"specimen-link\"}\n", + "]\n", + "item.contacts += [{\"name\": \"Specimen preparation\", \"email\": \"specimenpreparation@group.mail\"}]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:12.738354Z", + "iopub.status.busy": "2026-06-07T20:52:12.738192Z", + "iopub.status.idle": "2026-06-07T20:52:15.210359Z", + "shell.execute_reply": "2026-06-07T20:52:15.209237Z" + } + }, + "outputs": [], + "source": [ + "dsms.add(item)\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see now that the local system path of the attachment is changed to a simple 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": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:15.213492Z", + "iopub.status.busy": "2026-06-07T20:52:15.213316Z", + "iopub.status.idle": "2026-06-07T20:52:15.220028Z", + "shell.execute_reply": "2026-06-07T20:52:15.219136Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "kitem:\n", + " id: 1d2ba175-d61f-4e31-85f6-0bc6830b5cb8\n", + " name: Specimen-123\n", + " ktype_id: specimen\n", + " slug: specimen123-1d2ba175\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations:\n", + " - iri: https://w3id.org/pmd/co/Specimen\n", + " label: Specimen\n", + " namespace: https://w3id.org/pmd/co\n", + " attachments:\n", + " - name: testfile.txt\n", + " linked_kitems: []\n", + " affiliations: []\n", + " authors: []\n", + " contacts:\n", + " - name: Specimen preparation\n", + " email: specimenpreparation@group.mail\n", + " created_at: 2026-06-07 20:52:10.917226\n", + " updated_at: 2026-06-07 20:52:13.420118\n", + " external_links:\n", + " - label: specimen-link\n", + " url: http://specimens.org\n", + " apps: []\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", + " entries:\n", + " - id: input-specimen-width\n", + " type: Number\n", + " label: Width\n", + " value: 1\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " - id: input-specimen-length\n", + " type: Number\n", + " label: Length\n", + " value: 0.15\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.3. Updating access properties\n", + "\n", + "Access control entries can be updated in the same way as other KItem properties." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:15.222007Z", + "iopub.status.busy": "2026-06-07T20:52:15.221855Z", + "iopub.status.idle": "2026-06-07T20:52:15.392370Z", + "shell.execute_reply": "2026-06-07T20:52:15.390871Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/core/dsms.py:222: UserWarning: Nothing to commit. No changes have been made to the DSMS instance.If you would like to add&/delete KItems, KTypes or AppConfigs,please use: `dsms.add(my_object)` or dsms.delete(my_object)`before running `dsms.commit()`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from dsms.knowledge.properties.access import KItemAccessProperties, Role\n", + "\n", + "# Look up the current user to demonstrate role assignment\n", + "uname = dsms.config.username\n", + "if hasattr(uname, \"get_secret_value\"):\n", + " uname = uname.get_secret_value()\n", + "current_user = dsms.users.by_username.get(uname)\n", + "\n", + "item.access_properties = KItemAccessProperties(\n", + " user_access=[{\"user_id\": current_user.id, \"role\": Role.CONTRIBUTOR}],\n", + ")\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Furthermore we can also download the file we uploaded again:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:15.394803Z", + "iopub.status.busy": "2026-06-07T20:52:15.394632Z", + "iopub.status.idle": "2026-06-07T20:52:15.622891Z", + "shell.execute_reply": "2026-06-07T20:52:15.621435Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t\t\t Downloaded file: testfile.txt\n", + "|------------------------------------Beginning of file------------------------------------|\n", + "This is a specimen preparation protocol!\n", + "\n", + "|---------------------------------------End of file---------------------------------------|\n", + "\n", + "\n" + ] + } + ], + "source": [ + "for file in item.attachments:\n", + " download = file.download()\n", + "\n", + " print(\"\\t\\t\\t Downloaded file:\", file.name)\n", + " print(\"|------------------------------------Beginning of file------------------------------------|\")\n", + " print(download)\n", + " print(\"|---------------------------------------End of file---------------------------------------|\\n\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:15.625361Z", + "iopub.status.busy": "2026-06-07T20:52:15.625160Z", + "iopub.status.idle": "2026-06-07T20:52:16.116613Z", + "shell.execute_reply": "2026-06-07T20:52:16.115309Z" + } + }, + "outputs": [], + "source": [ + "# Clean up the tutorial item\n", + "del dsms[item]\n", + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/dsms_sdk/tutorials/3_updation.ipynb b/docs/dsms_sdk/tutorials/3_updation.ipynb deleted file mode 100644 index 178e205..0000000 --- a/docs/dsms_sdk/tutorials/3_updation.ipynb +++ /dev/null @@ -1,360 +0,0 @@ -{ - "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", - "\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": 5, - "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": 6, - "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": 7, - "metadata": {}, - "outputs": [], - "source": [ - "item = dsms[\"5cb3a95e-4aac-434d-81ba-7c242fd245aa\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "kitem:\n", - " id: 5cb3a95e-4aac-434d-81ba-7c242fd245aa\n", - " name: Specimen123\n", - " ktype_id: specimen\n", - " slug: specimen123-5cb3a95e\n", - " annotations: []\n", - " attachments:\n", - " - name: subgraph.ttl\n", - " linked_kitems: []\n", - " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", - " contacts: []\n", - " created_at: 2025-08-13 14:29:01.826691\n", - " updated_at: 2025-08-13 14:29:01.826691\n", - " external_links: []\n", - " apps: []\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", - " entries:\n", - " - id: id6c76bbffe7ca78\n", - " type: Number\n", - " label: Width\n", - " value: 0.5\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", - " relationMapping: null\n", - " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", - " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", - " relationMapping: null\n", - " required: false\n", - " rdf_exists: true\n", - " contexts: []" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item" - ] - }, - { - "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)), `list`accumulation (`+=` or `-=`), e.g. for the `annotations`, `attachments`, `external_link`, etc. \n", - "\n", - "**NOTE**: using `append` or `extend` will not validate the pydantic model of the respective fields and hence will cause an error during commiting. Hence, always use `+=`, `=` or `-=`.\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": 9, - "metadata": {}, - "outputs": [], - "source": [ - "item.name = \"Specimen-123\"\n", - "item.custom_properties.Width = 1\n", - "item.attachments += [\"testfile.txt\"]\n", - "item.annotations += [\"https://w3id.org/pmd/co/Specimen\"]\n", - "item.external_links += [\n", - " {\"url\": \"http://specimens.org\", \"label\": \"specimen-link\"}\n", - "]\n", - "item.contacts += [{\"name\": \"Specimen preparation\", \"email\": \"specimenpreparation@group.mail\"}]" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "dsms.add(item)\n", - "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": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "kitem:\n", - " id: 5cb3a95e-4aac-434d-81ba-7c242fd245aa\n", - " name: Specimen-123\n", - " ktype_id: specimen\n", - " slug: specimen123-5cb3a95e\n", - " annotations:\n", - " - iri: https://w3id.org/pmd/co/Specimen\n", - " label: Specimen\n", - " namespace: https://w3id.org/pmd/co\n", - " attachments:\n", - " - name: subgraph.ttl\n", - " - name: testfile.txt\n", - " linked_kitems: []\n", - " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", - " contacts:\n", - " - name: Specimen preparation\n", - " email: specimenpreparation@group.mail\n", - " created_at: 2025-08-13 14:29:01.826691\n", - " updated_at: 2025-08-13 14:30:26.915234\n", - " external_links:\n", - " - label: specimen-link\n", - " url: http://specimens.org\n", - " apps: []\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", - " entries:\n", - " - id: id6c76bbffe7ca78\n", - " type: Number\n", - " label: Width\n", - " value: 1\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", - " relationMapping: null\n", - " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", - " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", - " relationMapping: null\n", - " required: false\n", - " rdf_exists: true\n", - " contexts: []" - ] - }, - "execution_count": 11, - "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": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\t\t\t Downloaded file: subgraph.ttl\n", - "|------------------------------------Beginning of file------------------------------------|\n", - "@prefix ns1: .\n", - "@prefix ns2: .\n", - "@prefix rdfs: .\n", - "@prefix xsd: .\n", - "\n", - " a ;\n", - " rdfs:label \"Specimen-123\"^^xsd:string ;\n", - " ns1:EMMO_17e27c22_37e1_468c_9dd7_95e137f73e7f ,\n", - " ,\n", - " .\n", - "\n", - " a ns1:EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0 ;\n", - " ns2:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI ;\n", - " ns2:value 1 .\n", - "\n", - " a ns1:EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0 ;\n", - " ns2:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI ;\n", - " ns2:value \"0.2\"^^xsd:float .\n", - "\n", - " a ns1:EMMO_cd2cd0de_e0cc_4ef1_b27e_2e88db027bac ;\n", - " ns2:hasUnit \"http://qudt.org/vocab/unit/MilliM\"^^xsd:anyURI ;\n", - " ns2:value \"0.1\"^^xsd:float .\n", - "\n", - "\n", - "|---------------------------------------End of file---------------------------------------|\n", - "\n", - "\n", - "\t\t\t Downloaded file: testfile.txt\n", - "|------------------------------------Beginning of file------------------------------------|\n", - "This is a specimen preparation protocol!\n", - "\n", - "|---------------------------------------End of file---------------------------------------|\n", - "\n", - "\n" - ] - } - ], - "source": [ - "for file in item.attachments:\n", - " download = file.download()\n", - "\n", - " print(\"\\t\\t\\t Downloaded file:\", file.name)\n", - " print(\"|------------------------------------Beginning of file------------------------------------|\")\n", - " print(download)\n", - " print(\"|---------------------------------------End of file---------------------------------------|\\n\\n\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.11.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/dsms_sdk/tutorials/4_deletion.ipynb b/docs/dsms_sdk/tutorials/4_deletion.ipynb index fa033ad..3331135 100644 --- a/docs/dsms_sdk/tutorials/4_deletion.ipynb +++ b/docs/dsms_sdk/tutorials/4_deletion.ipynb @@ -6,7 +6,7 @@ "source": [ "# 4. Deleting KItems with the SDK\n", "\n", - "In this tutorial we see how to delete new Kitems and their properties." + "In this tutorial we see how to delete KItems and their properties." ] }, { @@ -15,18 +15,27 @@ "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 [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established 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." ] }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:20.126707Z", + "iopub.status.busy": "2026-06-07T20:52:20.126569Z", + "iopub.status.idle": "2026-06-07T20:52:20.769257Z", + "shell.execute_reply": "2026-06-07T20:52:20.768257Z" + } + }, "outputs": [], "source": [ - "from dsms import DSMS" + "from dsms import DSMS, KItem\n", + "from dsms.knowledge.properties.contacts import ContactInfo\n", + "from dsms.knowledge.properties.affiliations import Affiliation" ] }, { @@ -38,11 +47,19 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:20.772050Z", + "iopub.status.busy": "2026-06-07T20:52:20.771763Z", + "iopub.status.idle": "2026-06-07T20:52:21.285270Z", + "shell.execute_reply": "2026-06-07T20:52:21.284239Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" ] }, { @@ -54,11 +71,104 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:21.287942Z", + "iopub.status.busy": "2026-06-07T20:52:21.287782Z", + "iopub.status.idle": "2026-06-07T20:52:24.304520Z", + "shell.execute_reply": "2026-06-07T20:52:24.303800Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:430: UserWarning: A flat dictionary was provided for custom properties.\n", + " Will be transformed into `KItemCustomPropertiesModel`.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "kitem:\n", + " id: 401e2fad-8a68-4b95-b044-d023f10ae662\n", + " name: SpecimenToDelete\n", + " ktype_id: specimen\n", + " slug: specimentodelete-401e2fad\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations:\n", + " - iri: https://w3id.org/pmd/co/Specimen\n", + " label: Specimen\n", + " namespace: https://w3id.org/pmd/co\n", + " attachments:\n", + " - name: testfile.txt\n", + " linked_kitems: []\n", + " affiliations:\n", + " - name: Example Institute\n", + " authors: []\n", + " contacts:\n", + " - name: Tutorial Contact\n", + " email: contact@example.com\n", + " created_at: 2026-06-07 20:52:21.967502\n", + " updated_at: 2026-06-07 20:52:21.967502\n", + " external_links:\n", + " - label: Example link\n", + " url: https://example.com\n", + " apps: []\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", + " entries:\n", + " - id: input-specimen-width\n", + " type: Number\n", + " label: Width\n", + " value: 0.5\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " - id: input-specimen-length\n", + " type: Number\n", + " label: Length\n", + " value: 0.15\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "item = dsms[\"5cb3a95e-4aac-434d-81ba-7c242fd245aa\"]" + "# Create a Specimen with properties to demonstrate deletion\n", + "item = KItem(\n", + " name=\"SpecimenToDelete\",\n", + " ktype_id=dsms.ktypes.Specimen,\n", + " custom_properties={\"Width\": 0.5, \"Length\": 0.15},\n", + " annotations=[\"https://w3id.org/pmd/co/Specimen\"],\n", + " attachments=[\"testfile.txt\"],\n", + " external_links=[{\"url\": \"https://example.com\", \"label\": \"Example link\"}],\n", + " contacts=[ContactInfo(name=\"Tutorial Contact\", email=\"contact@example.com\")],\n", + " affiliations=[Affiliation(name=\"Example Institute\")],\n", + ")\n", + "dsms.add(item)\n", + "dsms.commit()\n", + "item" ] }, { @@ -83,18 +193,25 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:24.306977Z", + "iopub.status.busy": "2026-06-07T20:52:24.306818Z", + "iopub.status.idle": "2026-06-07T20:52:24.310641Z", + "shell.execute_reply": "2026-06-07T20:52:24.310036Z" + } + }, "outputs": [ { "data": { "text/plain": [ "contact:\n", - " name: Specimen preparation\n", - " email: specimenpreparation@group.mail" + " name: Tutorial Contact\n", + " email: contact@example.com" ] }, - "execution_count": 8, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -115,8 +232,15 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:24.312411Z", + "iopub.status.busy": "2026-06-07T20:52:24.312259Z", + "iopub.status.idle": "2026-06-07T20:52:24.314644Z", + "shell.execute_reply": "2026-06-07T20:52:24.314036Z" + } + }, "outputs": [], "source": [ "item.affiliations = []" @@ -131,14 +255,28 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:24.316340Z", + "iopub.status.busy": "2026-06-07T20:52:24.316191Z", + "iopub.status.idle": "2026-06-07T20:52:26.502185Z", + "shell.execute_reply": "2026-06-07T20:52:26.501032Z" + } + }, "outputs": [], "source": [ "dsms.add(item)\n", "dsms.commit()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** Some fields visible in KItem outputs (`authors`, `rdf_exists`, `user_groups`) are deprecated in v5.0.0 and are no longer populated by the server. They remain in the model for backward compatibility. Use `access_properties` for access control." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -148,70 +286,72 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:26.504688Z", + "iopub.status.busy": "2026-06-07T20:52:26.504528Z", + "iopub.status.idle": "2026-06-07T20:52:26.510827Z", + "shell.execute_reply": "2026-06-07T20:52:26.509943Z" + } + }, "outputs": [ { "data": { "text/plain": [ "kitem:\n", - " id: 5cb3a95e-4aac-434d-81ba-7c242fd245aa\n", - " name: Specimen-123\n", + " id: 401e2fad-8a68-4b95-b044-d023f10ae662\n", + " name: SpecimenToDelete\n", " ktype_id: specimen\n", - " slug: specimen123-5cb3a95e\n", + " slug: specimentodelete-401e2fad\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations: []\n", - " attachments:\n", - " - name: testfile.txt\n", + " attachments: []\n", " linked_kitems: []\n", - " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " affiliations:\n", + " - name: Example Institute\n", + " authors: []\n", " contacts:\n", - " - name: Specimen preparation\n", - " email: specimenpreparation@group.mail\n", - " created_at: 2025-08-13 14:29:01.826691\n", - " updated_at: 2025-08-13 14:30:26.915234\n", + " - name: Tutorial Contact\n", + " email: contact@example.com\n", + " created_at: 2026-06-07 20:52:21.967502\n", + " updated_at: 2026-06-07 20:52:21.967502\n", " external_links:\n", - " - label: specimen-link\n", - " url: http://specimens.org\n", + " - label: Example link\n", + " url: https://example.com\n", " apps: []\n", - " user_groups: []\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", " entries:\n", - " - id: id6c76bbffe7ca78\n", + " - id: input-specimen-width\n", " type: Number\n", " label: Width\n", - " value: 1\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " value: 0.5\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", + " - id: input-specimen-length\n", + " type: Number\n", " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " value: 0.15\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " rdf_exists: true\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []" ] }, - "execution_count": 11, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -229,8 +369,15 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:26.512275Z", + "iopub.status.busy": "2026-06-07T20:52:26.512127Z", + "iopub.status.idle": "2026-06-07T20:52:26.514536Z", + "shell.execute_reply": "2026-06-07T20:52:26.513876Z" + } + }, "outputs": [], "source": [ "del dsms[item]" @@ -246,13 +393,453 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:26.516218Z", + "iopub.status.busy": "2026-06-07T20:52:26.516075Z", + "iopub.status.idle": "2026-06-07T20:52:26.994428Z", + "shell.execute_reply": "2026-06-07T20:52:26.993049Z" + } + }, "outputs": [], "source": [ "dsms.commit()" ] }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:26.996981Z", + "iopub.status.busy": "2026-06-07T20:52:26.996825Z", + "iopub.status.idle": "2026-06-07T20:52:30.035281Z", + "shell.execute_reply": "2026-06-07T20:52:30.034334Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/core/dsms.py:339: DeprecationWarning: `kitems`-property is deprecated and only returns the 10 first kitems.\n", + " Please use the `get_kitems`-method instead.\n", + " warnings.warn(message, DeprecationWarning)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Project`. Cannot check if value is of correct type.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Tensile Strength [MPa]`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Material`. Cannot check if value is of correct type.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "kitems:\n", + "- id: 032a3d86-4705-47c0-969e-3251db82a1a6\n", + " name: my tensile test experiment\n", + " ktype_id: dataset\n", + " slug: mytensiletestexperiment-032a3d86\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments:\n", + " - name: dummy_data.csv\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-05 15:55:53.758604\n", + " updated_at: 2026-06-05 15:55:53.758604\n", + " external_links: []\n", + " apps:\n", + " - executable: testapp2\n", + " title: data2rdf\n", + " description: null\n", + " tags: null\n", + " additional_properties:\n", + " triggerUponUpload: true\n", + " triggerUponUploadFileExtensions:\n", + " - .csv\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "- id: 05884075-236c-483a-8427-1c9cd8886a04\n", + " name: test 2\n", + " ktype_id: metal-sheet\n", + " slug: test-2\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", + " linked_kitems:\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: bb98191c-9695-4980-a655-cad03abde223\n", + " name: Metal sheet test\n", + " ktype_id: metal-sheet\n", + " slug: metal-sheet-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: 5f2868e2-1196-4fb9-adb7-3b3d68e0a1f3\n", + " name: Specimen Extraction 2618A\n", + " ktype_id: manufacturing-process\n", + " slug: specimen-extraction-2618a\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-04 18:35:46.248292\n", + " updated_at: 2026-06-04 18:35:46.248292\n", + " external_links: []\n", + " apps: []\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " - role: CONTRIBUTOR\n", + " user_id: b76fa8d6-20d8-4a7e-a959-a975082b1c63\n", + " group_access: []\n", + " contexts:\n", + " - id: 249e1cdf-f584-4568-86cb-198757c66c15\n", + " name: metal sheet batch test\n", + " ktype_id: metal-sheet-batch\n", + " slug: metal-sheet-batch-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + "- id: 1053cd9b-221a-40cc-adb6-517cdcaa174c\n", + " name: My creep speciment\n", + " ktype_id: creep-specimen\n", + " slug: my-creep-speciment\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations:\n", + " - iri: https://w3id.org/steel/ProcessOntology/PercentageExtension\n", + " label: PercentageExtension\n", + " namespace: steelontology\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-05-28 08:49:52.034919\n", + " updated_at: 2026-05-28 08:49:52.034919\n", + " external_links: []\n", + " apps: []\n", + " summary: ''\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: section-creep-gauge\n", + " name: Gauge Section\n", + " entries:\n", + " - id: input-creep-gauge-length\n", + " type: Number\n", + " label: Gauge length\n", + " value: null\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasGaugeLengthLo_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: input-creep-gauge-diameter\n", + " type: Number\n", + " label: Gauge diameter\n", + " value: null\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasDiameter_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: section-creep-grip\n", + " name: Grip Ends\n", + " entries:\n", + " - id: input-creep-grip-type\n", + " type: Select\n", + " label: Grip type\n", + " value: null\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleType_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: input-creep-thread-spec\n", + " type: Text\n", + " label: Thread specification\n", + " value: null\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: https://w3id.org/steel/ProcessOntology/hasSampleInfos_Object\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "- id: 164c10c3-c84a-42de-abe0-41423d1091ad\n", + " name: Test catalog\n", + " ktype_id: dataset-catalog\n", + " slug: testcatalog-164c10c3\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-05 14:49:07.538315\n", + " updated_at: 2026-06-05 14:49:07.538315\n", + " external_links: []\n", + " apps: []\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: id178067094693834dl8n\n", + " name: Misc\n", + " entries:\n", + " - id: id1780670946938uw41nf\n", + " type: Text\n", + " label: Project\n", + " value: Mechanical testing campaign 1\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts:\n", + " - id: 6042535e-0a9e-4233-a09e-bd4464a695fc\n", + " name: Test dataset\n", + " ktype_id: dataset\n", + " slug: testdataset-6042535e\n", + " avatar_exists: false\n", + " has_contexts: false\n", + "- id: 1800e831-f267-4fa7-a1e6-1acda6dba9b0\n", + " name: Test\n", + " ktype_id: chemical-composition\n", + " slug: test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-05-03 16:39:29.875453\n", + " updated_at: 2026-05-03 16:39:29.875453\n", + " external_links: []\n", + " apps: []\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "- id: 227d1678-aa56-4598-9082-540c629fd8a8\n", + " name: Specimen123\n", + " ktype_id: specimen\n", + " slug: specimen123-227d1678\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-07 20:52:02.878207\n", + " updated_at: 2026-06-07 20:52:02.878207\n", + " external_links: []\n", + " apps: []\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", + " entries:\n", + " - id: input-specimen-width\n", + " type: Number\n", + " label: Width\n", + " value: 0.5\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " - id: input-specimen-length\n", + " type: Number\n", + " label: Length\n", + " value: 0.15\n", + " measurementUnit: null\n", + " relationMapping: null\n", + " required: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "- id: 249e1cdf-f584-4568-86cb-198757c66c15\n", + " name: metal sheet batch test\n", + " ktype_id: metal-sheet-batch\n", + " slug: metal-sheet-batch-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", + " linked_kitems:\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: 5f2868e2-1196-4fb9-adb7-3b3d68e0a1f3\n", + " name: Specimen Extraction 2618A\n", + " ktype_id: manufacturing-process\n", + " slug: specimen-extraction-2618a\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-04 14:22:32.661556\n", + " updated_at: 2026-06-04 14:22:32.661556\n", + " external_links: []\n", + " apps: []\n", + " summary: ''\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "- id: 2da465db-0840-4a1e-a24b-cc7355498958\n", + " name: Material test\n", + " ktype_id: material\n", + " slug: material-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-05-27 22:31:56.175890\n", + " updated_at: 2026-05-27 22:31:56.175890\n", + " external_links: []\n", + " apps: []\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "- id: 33d13760-d0ce-4117-bbef-c03970ae4d09\n", + " name: nb8-test-project\n", + " ktype_id: project\n", + " slug: nb8-test-project-33d13760\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-05 15:09:22.371655\n", + " updated_at: 2026-06-05 15:09:22.371655\n", + " external_links: []\n", + " apps: []\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts:\n", + " - id: e2538d23-d9bd-4151-94c4-c691d89613a6\n", + " name: nb8-test-dataset\n", + " ktype_id: dataset\n", + " slug: nb8-test-dataset-e2538d23\n", + " avatar_exists: false\n", + " has_contexts: false\n", + "- id: 3ddb5765-3fda-4ed2-9e7a-5459197ffe6f\n", + " name: Test project\n", + " ktype_id: project\n", + " slug: testproject-3ddb5765\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems: []\n", + " affiliations: []\n", + " contacts: []\n", + " created_at: 2026-06-05 15:51:44.846169\n", + " updated_at: 2026-06-05 15:51:44.846169\n", + " external_links: []\n", + " apps: []\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []\n", + "total_count: 39" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dsms.kitems" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -281,7 +868,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/5_search.ipynb b/docs/dsms_sdk/tutorials/5_search.ipynb index 9cfd7c0..26ec61d 100644 --- a/docs/dsms_sdk/tutorials/5_search.ipynb +++ b/docs/dsms_sdk/tutorials/5_search.ipynb @@ -6,7 +6,7 @@ "source": [ "# 5. Searching KItems with the SDK\n", "\n", - "In this tutorial we see how to search existing Kitems" + "In this tutorial we see how to search existing KItems" ] }, { @@ -14,7 +14,7 @@ "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", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established 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." @@ -23,7 +23,14 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:33.740072Z", + "iopub.status.busy": "2026-06-07T20:52:33.739898Z", + "iopub.status.idle": "2026-06-07T20:52:34.393563Z", + "shell.execute_reply": "2026-06-07T20:52:34.392399Z" + } + }, "outputs": [], "source": [ "from dsms import DSMS, KItem" @@ -39,10 +46,18 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:34.396930Z", + "iopub.status.busy": "2026-06-07T20:52:34.396592Z", + "iopub.status.idle": "2026-06-07T20:52:34.904662Z", + "shell.execute_reply": "2026-06-07T20:52:34.903487Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" ] }, { @@ -56,7 +71,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this section, we would like to search for specfic KItems we created in the DSMS.\n", + "In this section, we would like to search for specific 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", @@ -68,25 +83,23 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:34.908633Z", + "iopub.status.busy": "2026-06-07T20:52:34.908447Z", + "iopub.status.idle": "2026-06-07T20:52:46.334364Z", + "shell.execute_reply": "2026-06-07T20:52:46.333400Z" + } + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/app/dsms/knowledge/kitem.py:406: UserWarning: A flat dictionary was provided for custom properties.\n", - " Will be transformed into `KItemCustomPropertiesModel`.\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:330: UserWarning: Found a to be linked instead of an . Will link it with the default relationship 'dcterms:haspart'.\n", " warnings.warn(\n", - "/app/dsms/knowledge/kitem.py:306: UserWarning: Found a to be linked instead of an . Will link it with the default relationship 'dcterms:haspart'.\n", - " warnings.warn(\n", - "/usr/local/lib/python3.11/site-packages/pydantic/main.py:463: UserWarning: Pydantic serializer warnings:\n", - " PydanticSerializationUnexpectedValue(Expected `str` - serialized value may not be as expected [input_value=False, input_type=bool])\n", - " return self.__pydantic_serializer__.to_python(\n", - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Producer`. Cannot check if value is of correct type.\n", - " warnings.warn(\n", - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Room Number`. Cannot check if value is of correct type.\n", - " warnings.warn(\n", - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Description`. Cannot check if value is of correct type.\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:430: UserWarning: A flat dictionary was provided for custom properties.\n", + " Will be transformed into `KItemCustomPropertiesModel`.\n", " warnings.warn(\n" ] } @@ -94,22 +107,12 @@ "source": [ "item1 = KItem(\n", " name=\"Machine-1\",\n", - " ktype_id=dsms.ktypes.Testingmachine,\n", - " annotations=[\"https://w3id.org/steel/ProcessOntology/TestingMachine\"],\n", - " custom_properties={\"Producer\": \"TestingLab GmBH\",\n", - " \"Room Number\": \"A404\",\n", - " \"Description\": \"Bending Test Machine\"\n", - " }\n", + " ktype_id=dsms.ktypes.MeasurementDevice\n", ")\n", "\n", "item2 = KItem(\n", " name=\"Machine-2\",\n", - " ktype_id=dsms.ktypes.Testingmachine,\n", - " annotations=[\"https://w3id.org/steel/ProcessOntology/TestingMachine\"],\n", - " custom_properties={\"Producer\": \"StressStrain GmBH\",\n", - " \"Room Number\": \"B500\",\n", - " \"Description\": \"Compression Test Machine\"\n", - " }\n", + " ktype_id=dsms.ktypes.MeasurementDevice\n", ")\n", "\n", "item3 = KItem(\n", @@ -119,7 +122,7 @@ " annotations=[\"https://w3id.org/steel/ProcessOntology/TestPiece\"],\n", " custom_properties = {\n", " \"Width\": 0.5,\n", - " \"Length\": [0.1, 0.2],\n", + " \"Length\": 0.15,\n", " }\n", "\n", ")\n", @@ -130,7 +133,7 @@ " annotations=[\"https://w3id.org/steel/ProcessOntology/TestPiece\"],\n", " custom_properties = {\n", " \"Width\": 0.8,\n", - " \"Length\": [0.8, 0.9],\n", + " \"Length\": 0.85,\n", " }\n", ")\n", "\n", @@ -145,143 +148,162 @@ "dsms.commit()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** Some fields visible in KItem outputs (`authors`, `rdf_exists`, `user_groups`) are deprecated in v5.0.0 and are no longer populated by the server. They remain in the model for backward compatibility. Use `access_properties` for access control." + ] + }, { "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", + "

Note : Here in this tutorial, we use dsms.search with `limit=2` 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`:" + "Now, we can search for e.g. KItems of type `MeasurementDevice`:" ] }, { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:46.337676Z", + "iopub.status.busy": "2026-06-07T20:52:46.337509Z", + "iopub.status.idle": "2026-06-07T20:52:47.033167Z", + "shell.execute_reply": "2026-06-07T20:52:47.032218Z" + } + }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Material State`. Cannot check if value is of correct type.\n", + " warnings.warn(\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ "hits:\n", "- kitem:\n", - " id: 14e9a531-7f91-4dd5-a72d-f8753a84d1ab\n", - " name: Specimen123\n", + " id: f2320732-5825-404a-a5bc-2283267569ef\n", + " name: Over-aged HP-180-1 after ageing\n", " ktype_id: specimen\n", - " slug: specimen123-14e9a531\n", - " annotations: []\n", - " attachments:\n", - " - name: subgraph.ttl\n", - " linked_kitems: []\n", + " slug: over-agedhp-180-1afterageing-f2320732\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " annotations:\n", + " - iri: https://w3id.org/ORCHESTER/heat-treatment/AgedSpecimen\n", + " label: AgedSpecimen\n", + " namespace: https://w3id.org/ORCHESTER/heat-treatment\n", + " attachments: []\n", + " linked_kitems:\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: fe7692ed-c3aa-44dd-9a65-4ec1400ba358\n", + " name: Heat treatment for HP-180-1\n", + " ktype_id: manufacturing-process\n", + " slug: heattreatmentforhp-180-1-fe7692ed\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", " contacts: []\n", - " created_at: 2025-08-12 14:41:51.234220\n", - " updated_at: 2025-08-12 14:41:51.234220\n", + " created_at: 2025-02-14 08:44:54.658452\n", + " updated_at: 2025-02-14 08:44:54.658452\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", + " summary: \"Specimen HP-180-1 after ageing with 180 \\xB0C, 1.1 h\"\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: id17395226943636hrlge\n", + " name: Misc\n", " entries:\n", - " - id: id6c76bbffe7ca78\n", - " type: Number\n", - " label: Width\n", - " value: 0.5\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", - " relationMapping: null\n", - " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", - " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " - id: id17395226943631a7aru\n", + " type: Text\n", + " label: Material State\n", + " value: \"180 \\xB0C, 1.1 h\"\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " rdf_exists: true\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: b556dd81-c80c-4661-bca9-6d7b8614b1fe\n", + " group_access: []\n", " contexts: []\n", " fuzzy: false\n", "- kitem:\n", - " id: 1f3694d3-4624-45b8-9182-3bf88c6511f9\n", - " name: Specimen-123\n", + " id: c42a54a0-0d6d-4691-b3d7-b7fc51902886\n", + " name: ABR4RTd2 before ageing\n", " ktype_id: specimen\n", - " slug: specimen123-1f3694d3\n", - " annotations:\n", - " - iri: https://w3id.org/pmd/co/Specimen\n", - " label: Specimen\n", - " namespace: https://w3id.org/pmd/co\n", - " attachments:\n", - " - name: subgraph.ttl\n", - " - name: testfile.txt\n", - " linked_kitems: []\n", + " slug: abr4rtd2beforeageing-c42a54a0\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments: []\n", + " linked_kitems:\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: d3b4f599-a37b-47cc-ac0c-78ccfff3b604\n", + " name: Heat treatment for ABR4RTd2\n", + " ktype_id: manufacturing-process\n", + " slug: heattreatmentforabr4rtd2-d3b4f599\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: 5f2868e2-1196-4fb9-adb7-3b3d68e0a1f3\n", + " name: Specimen Extraction 2618A\n", + " ktype_id: manufacturing-process\n", + " slug: specimen-extraction-2618a\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", - " contacts:\n", - " - name: Specimen preparation\n", - " email: specimenpreparation@group.mail\n", - " created_at: 2025-08-13 13:08:52.481587\n", - " updated_at: 2025-08-13 14:23:47.730855\n", - " external_links:\n", - " - label: specimen-link\n", - " url: http://specimens.org\n", + " contacts: []\n", + " created_at: 2025-02-14 08:43:27.618862\n", + " updated_at: 2025-02-14 08:43:27.618862\n", + " external_links: []\n", " apps: []\n", - " user_groups: []\n", + " summary: \"ABR4RTd2 at T61 state before ageing with 160 \\xB0C, 8760 h\"\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: id1739522607214d9ypef\n", + " name: Misc\n", " entries:\n", - " - id: id6c76bbffe7ca78\n", - " type: Number\n", - " label: Width\n", - " value: 1\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " - id: id173952260721459lyeg\n", + " type: Text\n", + " label: Material State\n", + " value: T61\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", - " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", - " relationMapping: null\n", - " required: false\n", - " rdf_exists: true\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: b556dd81-c80c-4661-bca9-6d7b8614b1fe\n", + " group_access: []\n", " contexts: []\n", " fuzzy: false\n", - "total_count: 5\n", + "total_count: 184\n", "\n", "Name of the first kitem:\n", - "Specimen123\n" + "Over-aged HP-180-1 after ageing\n" ] } ], @@ -296,127 +318,480 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "... and for all of type `Organization` and `Testingmachine`:" + "... and for all of type `Organization` and `MeasurementDevice`:" ] }, { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:47.035596Z", + "iopub.status.busy": "2026-06-07T20:52:47.035438Z", + "iopub.status.idle": "2026-06-07T20:52:47.834132Z", + "shell.execute_reply": "2026-06-07T20:52:47.832998Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "kitem:\n", - " id: 4d587532-4275-4f4f-9d19-e199d9452d13\n", - " name: Research Institute ABC\n", + " id: 0509a638-227b-4a57-a381-5a591748facb\n", + " name: Fraunhofer IWM\n", " ktype_id: organization\n", - " slug: researchinstituteabc-4d587532\n", + " slug: fraunhofer-iwm\n", + " avatar_exists: true\n", + " has_contexts: false\n", " annotations:\n", - " - iri: www.researchBACiri.org/foo\n", - " label: foo\n", - " namespace: www.researchBACiri.org\n", + " - iri: https://w3id.org/steel/ProcessOntology/TestingFacility\n", + " label: TestingFacility\n", + " namespace: steel-ontology\n", + " - iri: https://w3id.org/steel/ProcessOntology/MaterialsScience\n", + " label: MaterialsScience\n", + " namespace: steel-ontology\n", + " - iri: https://w3id.org/steel/ProcessOntology/Manufacturing\n", + " label: Manufacturing\n", + " namespace: steel-ontology\n", " attachments:\n", - " - name: subgraph.ttl\n", + " - name: KG.kitem.ttl\n", " linked_kitems:\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: 3c4585fb-eec5-4808-8e73-fc853c8a4a0e\n", + " name: \"Johannes Preu\\xDFner\"\n", + " ktype_id: expert\n", + " slug: johannes-preussner\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: 00e8b5a3-c65d-4d1a-85d4-5607e8048224\n", + " name: Yoav Nahshon\n", + " ktype_id: expert\n", + " slug: yoav-nahshon\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id220e33fbd6ce1\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: b10cb7e7-c48e-4405-b7ba-7788ee425db4\n", + " name: ORCHESTER\n", + " ktype_id: project\n", + " slug: orchester\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: idb13d750ebf668\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: 7dc50ec2-ce28-4c8b-be94-de533f7b71c7\n", + " name: \"Gesch\\xE4ftsfeld Werkstoffbewertung und Lebensdauerkonzepte\"\n", + " ktype_id: organization\n", + " slug: geschftsfeld-werkstoffbewertung-und-lebe\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id2af8ad6c4bb5a\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: bb98191c-9695-4980-a655-cad03abde223\n", + " name: Metal sheet test\n", + " ktype_id: metal-sheet\n", + " slug: metal-sheet-test\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: f7114f25-a437-430d-b4a3-cb58b6d28b34\n", + " name: Dirk Helm\n", + " ktype_id: expert\n", + " slug: dirk-helm\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id220e33fbd6ce1\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: f382ef00-bb80-4373-96c8-59b302b96d41\n", + " name: Alu-Vorprojekte\n", + " ktype_id: project\n", + " slug: alu-vorprojekte\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: idb13d750ebf668\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: fc8b2c3e-358d-46a9-8f5d-811a43f90875\n", + " name: \"Matthias B\\xFCschelberger\"\n", + " ktype_id: expert\n", + " slug: matthias-bschelberger\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id220e33fbd6ce1\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: 8ea65573-67fe-4e7b-85ce-e2edd512e192\n", + " name: High Cycle Fatigue EN AW-2618A\n", + " ktype_id: dataset-catalog\n", + " slug: high-cycle-fatigue-en-aw-2618a\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: b0c08a47-ad86-459d-83a7-c33310731790\n", + " name: \"Gesch\\xE4ftsfeld Fertigungsprozesse\"\n", + " ktype_id: organization\n", + " slug: geschftsfeld-fertigungsprozesse\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id2af8ad6c4bb5a\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: 97611992-97cd-40e2-bec8-2c0becab07d8\n", + " name: Christoph Schweizer\n", + " ktype_id: expert\n", + " slug: christoph-schweizer\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id220e33fbd6ce1\n", " - is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 0f89d6ad-3446-4d8a-9f27-39a69bcffeee\n", - " name: Machine-1\n", - " ktype_id: testingmachine\n", - " slug: machine-1-0f89d6ad\n", + " id: 455d854f-889e-48e6-8924-7fc78e77fe00\n", + " name: Final Report FVV No. 1390\n", + " ktype_id: document\n", + " slug: final-report-fvv-no-1390\n", + " avatar_exists: true\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", " - is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", - " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2\n", + " id: 053142ba-d882-441a-b8c5-ea8f889e8527\n", + " name: Damage Parameter DFC for creep-fatigue-interaction\n", + " ktype_id: material-model\n", + " slug: damage-parameter-dfc-for-creep-fatigue-i\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: c83db95e-bef8-4bd5-a2fc-ab645437309d\n", + " name: Universal sheet metal testing machine\n", + " ktype_id: measurement-device\n", + " slug: universal-sheet-metal-testing-machine-bu\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id7550e1c6439dd\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: eacb6b34-48ce-4ae0-83f0-b5f0a2d376fa\n", + " name: StahlDigital\n", + " ktype_id: project\n", + " slug: stahldigital\n", + " avatar_exists: false\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: idb13d750ebf668\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: abbe76e4-e09c-4448-ad8e-56ec8d5843f0\n", + " name: Fraunhofer\n", + " ktype_id: organization\n", + " slug: fraunhofer\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id2af8ad6c4bb5a\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: e6da3725-ecfd-4658-82ac-9058a118fb26\n", + " name: Fraunhofer IWM\n", + " ktype_id: address\n", + " slug: fraunhofer-iwm\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id5597c3fd52603\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", " contacts: []\n", - " created_at: 2025-08-13 14:33:33.681221\n", - " updated_at: 2025-08-13 14:33:33.681221\n", - " external_links: []\n", + " created_at: 2024-07-28 21:17:03.942225\n", + " updated_at: 2024-07-28 21:17:03.942225\n", + " external_links:\n", + " - label: _u_r_l\n", + " url: https://www.iwm.fraunhofer.de/\n", " apps: []\n", - " user_groups: []\n", - " rdf_exists: true\n", + " summary: \"Das Fraunhofer-Institut f\\xFCr Werkstoffmechanik IWM in Freiburg ist eine\\\n", + " \\ Einrichtung der Fraunhofer-Gesellschaft zur F\\xF6rderung der angewandten Forschung\\\n", + " \\ e.V. Das Fraunhofer IWM ist Forschungs- und Entwicklungspartner der Industrie\\\n", + " \\ und von \\xF6ffentlichen Auftraggebern zu Themen der Sicherheit, Zuverl\\xE4ssigkeit,\\\n", + " \\ Lebensdauer und Funktionalit\\xE4t von Werkstoffen in Bauteilen und Fertigungsverfahren.\"\n", + " custom_properties:\n", + " content:\n", + " sections:\n", + " - id: id21866f42d5176\n", + " name: Untitled Section\n", + " entries:\n", + " - id: id4d61120f8538b\n", + " type: Text\n", + " label: Name of the Organisation\n", + " value: Fraunhofer IWM\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: id2af8ad6c4bb5a\n", + " type: Knowledge item\n", + " label: Sub Organisation of\n", + " value:\n", + " - id: abbe76e4-e09c-4448-ad8e-56ec8d5843f0\n", + " name: Fraunhofer\n", + " ktype_id: organization\n", + " slug: fraunhofer\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/org#subOrganizationOf\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: id5880a22a80ecf\n", + " type: Text\n", + " label: URL of the webpage\n", + " value: https://www.iwm.fraunhofer.de\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/dcat#accessURL\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: id3e32c796f00c7\n", + " type: Text\n", + " label: City\n", + " value: Freiburg\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/org#location\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: id5597c3fd52603\n", + " type: Knowledge item\n", + " label: Address\n", + " value:\n", + " - id: e6da3725-ecfd-4658-82ac-9058a118fb26\n", + " name: Fraunhofer IWM\n", + " ktype_id: address\n", + " slug: fraunhofer-iwm\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/org#location\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 1f6980ba-f01e-4d32-83d5-82397db4c5e9\n", + " group_access: []\n", " contexts: []\n", "\n", "fuzziness: False\n", "\n", "\n", "kitem:\n", - " id: 0f89d6ad-3446-4d8a-9f27-39a69bcffeee\n", - " name: Machine-1\n", - " ktype_id: testingmachine\n", - " slug: machine-1-0f89d6ad\n", - " annotations:\n", - " - iri: https://w3id.org/steel/ProcessOntology/TestingMachine\n", - " label: TestingMachine\n", - " namespace: https://w3id.org/steel/ProcessOntology\n", - " attachments: []\n", + " id: 0eb837b5-dd1d-48dc-8ee7-89d4c373d0a2\n", + " name: Gruppe Werkstoffcharakterisierung (IWS)\n", + " ktype_id: organization\n", + " slug: gruppe-werkstoffcharakterisierung-iws\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", + " attachments:\n", + " - name: KG.kitem.ttl\n", " linked_kitems:\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: 81fa7693-ca78-443a-99e4-f8233e5e765b\n", + " name: ORC_S1_V2_01_316L_14057_ab_QS\n", + " ktype_id: specimen\n", + " slug: orcs1v201316l14057abqs\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", " - is_incoming: true\n", " label: Has Part\n", " kitem:\n", - " id: 4d587532-4275-4f4f-9d19-e199d9452d13\n", - " name: Research Institute ABC\n", + " id: ee7206b8-aeb2-4b6b-9679-159e548b215a\n", + " name: Leonid Gerdt\n", + " ktype_id: expert\n", + " slug: leonid-gerdt\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: 04f1d7d3-4bd4-43d1-ae33-d8f50814dcc3\n", + " name: ORC_S1_V1_08_316L_PANACEA_WB_LS\n", + " ktype_id: specimen\n", + " slug: orcs1v108316lpanaceawbls\n", + " avatar_exists: true\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: false\n", + " label: Has Part\n", + " kitem:\n", + " id: 173c87c7-298e-451c-ad63-2933f5f2d907\n", + " name: Fraunhofer IWS\n", " ktype_id: organization\n", - " slug: researchinstituteabc-4d587532\n", + " slug: fraunhofer-iws\n", + " avatar_exists: true\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id2af8ad6c4bb5a\n", " - is_incoming: true\n", " label: Has Part\n", " kitem:\n", - " id: d1ce4f7e-223b-4086-b5e5-11017f47270d\n", - " name: Specimen-1\n", - " ktype_id: specimen\n", - " slug: specimen-1-d1ce4f7e\n", + " id: d7d328d4-174d-4b64-ba81-574ed808e68d\n", + " name: \"Philip Tr\\xE4ger\"\n", + " ktype_id: expert\n", + " slug: philip-traeger\n", + " avatar_exists: true\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", + " - is_incoming: true\n", + " label: Has Part\n", + " kitem:\n", + " id: fb7ca4e0-c80c-4b7c-b6f0-e8515deac2f5\n", + " name: Sebastian Biastoch\n", + " ktype_id: expert\n", + " slug: sebastian-biastoch\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " iri: http://purl.org/dc/terms/hasPart\n", + " generated_by: id220e33fbd6ce1\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", " contacts: []\n", - " created_at: 2025-08-13 14:33:29.742517\n", - " updated_at: 2025-08-13 14:33:29.742517\n", + " created_at: 2024-08-01 09:09:33.415077\n", + " updated_at: 2024-08-01 09:09:33.415077\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: id17550956094374867vv\n", - " name: Misc\n", + " - id: id21866f42d5176\n", + " name: Untitled Section\n", " entries:\n", - " - id: id1755095609437i4mybj\n", + " - id: id4d61120f8538b\n", " type: Text\n", - " label: Producer\n", - " value: TestingLab GmBH\n", + " label: Name of the Organisation\n", + " value: Gruppe Werkstoffcharakterisierung (IWS)\n", " measurementUnit: null\n", - " relationMapping: null\n", + " relationMapping:\n", + " iri: http://xmlns.com/foaf/0.1/name\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", " required: false\n", - " - id: id1755095609437hd07kh\n", + " - id: id2af8ad6c4bb5a\n", + " type: Knowledge item\n", + " label: Sub Organisation of\n", + " value:\n", + " - id: 173c87c7-298e-451c-ad63-2933f5f2d907\n", + " name: Fraunhofer IWS\n", + " ktype_id: organization\n", + " slug: fraunhofer-iws\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/org#subOrganizationOf\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: id5880a22a80ecf\n", " type: Text\n", - " label: Room Number\n", - " value: A404\n", + " label: URL of the webpage\n", + " value: null\n", " measurementUnit: null\n", - " relationMapping: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/dcat#accessURL\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", " required: false\n", - " - id: id17550956094377bpur4\n", + " - id: id3e32c796f00c7\n", " type: Text\n", - " label: Description\n", - " value: Bending Test Machine\n", + " label: City\n", + " value: Dresden\n", " measurementUnit: null\n", - " relationMapping: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/org#location\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", + " required: false\n", + " - id: id5597c3fd52603\n", + " type: Knowledge item\n", + " label: Address\n", + " value: null\n", + " measurementUnit: null\n", + " relationMapping:\n", + " iri: http://www.w3.org/ns/org#location\n", + " label: null\n", + " type: data_property\n", + " classIri: null\n", + " inverse: false\n", " required: false\n", - " rdf_exists: false\n", + " access_properties:\n", + " visibility: internal\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 7e842ac6-3642-49bb-971d-e991d991a8e0\n", + " group_access: []\n", " contexts: []\n", "\n", "fuzziness: False\n", @@ -426,7 +801,7 @@ } ], "source": [ - "for result in dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.Testingmachine], limit=2):\n", + "for result in dsms.search(ktypes=[dsms.ktypes.Organization, dsms.ktypes.MeasurementDevice], limit=2):\n", " print(result.kitem)\n", " print(\"fuzziness: \", result.fuzzy)\n", " print(\"\\n\")\n", @@ -443,70 +818,69 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:47.836354Z", + "iopub.status.busy": "2026-06-07T20:52:47.836183Z", + "iopub.status.idle": "2026-06-07T20:52:48.252301Z", + "shell.execute_reply": "2026-06-07T20:52:48.251325Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "kitem:\n", - " id: 1f3694d3-4624-45b8-9182-3bf88c6511f9\n", + " id: 83bbae03-6067-4b07-9713-33c53198f372\n", " name: Specimen-123\n", " ktype_id: specimen\n", - " slug: specimen123-1f3694d3\n", + " slug: specimen123-83bbae03\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations:\n", " - iri: https://w3id.org/pmd/co/Specimen\n", " label: Specimen\n", " namespace: https://w3id.org/pmd/co\n", " attachments:\n", - " - name: subgraph.ttl\n", " - name: testfile.txt\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", " contacts:\n", " - name: Specimen preparation\n", " email: specimenpreparation@group.mail\n", - " created_at: 2025-08-13 13:08:52.481587\n", - " updated_at: 2025-08-13 14:23:47.730855\n", + " created_at: 2026-06-05 15:45:36.007534\n", + " updated_at: 2026-06-05 15:45:38.058192\n", " external_links:\n", " - label: specimen-link\n", " url: http://specimens.org\n", " apps: []\n", - " user_groups: []\n", " custom_properties:\n", " content:\n", " sections:\n", - " - id: idb424123144cdd8\n", - " name: Untitled Section\n", + " - id: section-specimen-info\n", + " name: Specimen Information\n", " entries:\n", - " - id: id6c76bbffe7ca78\n", + " - id: input-specimen-width\n", " type: Number\n", " label: Width\n", " value: 1\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " - id: id717d07130a7618\n", - " type: Slider\n", + " - id: input-specimen-length\n", + " type: Number\n", " label: Length\n", - " value:\n", - " - 0.1\n", - " - 0.2\n", - " measurementUnit:\n", - " iri: http://qudt.org/vocab/unit/MilliM\n", - " label: Millimetre\n", - " symbol: null\n", - " namespace: http://qudt.org/vocab/unit\n", + " value: 0.15\n", + " measurementUnit: null\n", " relationMapping: null\n", " required: false\n", - " rdf_exists: true\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []\n", "fuzzy: false\n", "\n" @@ -528,51 +902,64 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.254274Z", + "iopub.status.busy": "2026-06-07T20:52:48.254119Z", + "iopub.status.idle": "2026-06-07T20:52:48.675410Z", + "shell.execute_reply": "2026-06-07T20:52:48.674414Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "kitem:\n", - " id: 4d587532-4275-4f4f-9d19-e199d9452d13\n", + " id: 15f7b8a8-a0ab-450e-80c4-fafc0e454f8c\n", " name: Research Institute ABC\n", " ktype_id: organization\n", - " slug: researchinstituteabc-4d587532\n", + " slug: researchinstituteabc-15f7b8a8\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations:\n", " - iri: www.researchBACiri.org/foo\n", " label: foo\n", " namespace: www.researchBACiri.org\n", - " attachments:\n", - " - name: subgraph.ttl\n", + " attachments: []\n", " linked_kitems:\n", " - is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", " - is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 0f89d6ad-3446-4d8a-9f27-39a69bcffeee\n", + " id: 61a91ea0-53c4-4711-aa59-c1d33f75a640\n", " name: Machine-1\n", - " ktype_id: testingmachine\n", - " slug: machine-1-0f89d6ad\n", + " ktype_id: measurement-device\n", + " slug: machine-1-61a91ea0\n", + " avatar_exists: false\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", " contacts: []\n", - " created_at: 2025-08-13 14:33:33.681221\n", - " updated_at: 2025-08-13 14:33:33.681221\n", + " created_at: 2026-06-07 20:52:44.716348\n", + " updated_at: 2026-06-07 20:52:44.716348\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", - " rdf_exists: true\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []\n", "fuzzy: false\n", "\n" @@ -590,20 +977,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 5.3. Fetching linked KItems from a KItem" + "### 5.3. Fetching linked KItems from a KItem" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In the beginning under **5.1** we created some kitems and linked each other. Now we want to fetch the linked kitems and display them to the user. For this we use the `linked_kitems` attribute." + "In section **5.2** we created some KItems and linked each other. Now we want to fetch the linked KItems and display them to the user. For this we use the `linked_kitems` attribute." ] }, { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.677660Z", + "iopub.status.busy": "2026-06-07T20:52:48.677493Z", + "iopub.status.idle": "2026-06-07T20:52:48.684164Z", + "shell.execute_reply": "2026-06-07T20:52:48.683492Z" + } + }, "outputs": [ { "data": { @@ -611,18 +1005,22 @@ "- is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart\n", "- is_incoming: false\n", " label: Has Part\n", " kitem:\n", - " id: 0f89d6ad-3446-4d8a-9f27-39a69bcffeee\n", + " id: 61a91ea0-53c4-4711-aa59-c1d33f75a640\n", " name: Machine-1\n", - " ktype_id: testingmachine\n", - " slug: machine-1-0f89d6ad\n", + " ktype_id: measurement-device\n", + " slug: machine-1-61a91ea0\n", + " avatar_exists: false\n", + " has_contexts: false\n", " iri: http://purl.org/dc/terms/hasPart" ] }, @@ -645,60 +1043,42 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.685827Z", + "iopub.status.busy": "2026-06-07T20:52:48.685687Z", + "iopub.status.idle": "2026-06-07T20:52:48.690015Z", + "shell.execute_reply": "2026-06-07T20:52:48.689396Z" + } + }, "outputs": [ { "data": { "text/plain": [ "kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2\n", - " annotations:\n", - " - iri: https://w3id.org/steel/ProcessOntology/TestingMachine\n", - " label: TestingMachine\n", - " namespace: https://w3id.org/steel/ProcessOntology\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", " attachments: []\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " authors: []\n", " contacts: []\n", - " created_at: 2025-08-13 14:33:30.358137\n", - " updated_at: 2025-08-13 14:33:30.358137\n", + " created_at: 2026-06-07 20:52:38.475617\n", + " updated_at: 2026-06-07 20:52:38.475617\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: id1755095609461z4tdh1\n", - " name: Misc\n", - " entries:\n", - " - id: id1755095609461bqgrqv\n", - " type: Text\n", - " label: Producer\n", - " value: StressStrain GmBH\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id17550956094616vktvr\n", - " type: Text\n", - " label: Room Number\n", - " value: B500\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id1755095609461dhszfo\n", - " type: Text\n", - " label: Description\n", - " value: Compression Test Machine\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []" ] }, @@ -721,21 +1101,32 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.691717Z", + "iopub.status.busy": "2026-06-07T20:52:48.691578Z", + "iopub.status.idle": "2026-06-07T20:52:48.695187Z", + "shell.execute_reply": "2026-06-07T20:52:48.694707Z" + } + }, "outputs": [ { "data": { "text/plain": [ "{'http://purl.org/dc/terms/hasPart': [kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2,\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false,\n", " kitem:\n", - " id: 0f89d6ad-3446-4d8a-9f27-39a69bcffeee\n", + " id: 61a91ea0-53c4-4711-aa59-c1d33f75a640\n", " name: Machine-1\n", - " ktype_id: testingmachine\n", - " slug: machine-1-0f89d6ad]}" + " ktype_id: measurement-device\n", + " slug: machine-1-61a91ea0\n", + " avatar_exists: false\n", + " has_contexts: false]}" ] }, "execution_count": 10, @@ -750,60 +1141,42 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.696898Z", + "iopub.status.busy": "2026-06-07T20:52:48.696759Z", + "iopub.status.idle": "2026-06-07T20:52:48.701595Z", + "shell.execute_reply": "2026-06-07T20:52:48.700805Z" + } + }, "outputs": [ { "data": { "text/plain": [ "kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2\n", - " annotations:\n", - " - iri: https://w3id.org/steel/ProcessOntology/TestingMachine\n", - " label: TestingMachine\n", - " namespace: https://w3id.org/steel/ProcessOntology\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", " attachments: []\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " authors: []\n", " contacts: []\n", - " created_at: 2025-08-13 14:33:30.358137\n", - " updated_at: 2025-08-13 14:33:30.358137\n", + " created_at: 2026-06-07 20:52:38.475617\n", + " updated_at: 2026-06-07 20:52:38.475617\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: id1755095609461z4tdh1\n", - " name: Misc\n", - " entries:\n", - " - id: id1755095609461bqgrqv\n", - " type: Text\n", - " label: Producer\n", - " value: StressStrain GmBH\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id17550956094616vktvr\n", - " type: Text\n", - " label: Room Number\n", - " value: B500\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id1755095609461dhszfo\n", - " type: Text\n", - " label: Description\n", - " value: Compression Test Machine\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []" ] }, @@ -826,25 +1199,159 @@ { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.703110Z", + "iopub.status.busy": "2026-06-07T20:52:48.702972Z", + "iopub.status.idle": "2026-06-07T20:52:48.712002Z", + "shell.execute_reply": "2026-06-07T20:52:48.711212Z" + } + }, "outputs": [ { "data": { "text/plain": [ "{ktype:\n", - " id: testingmachine\n", - " name: TestingMachine\n", - " created_at: '2025-07-22T12:41:50.505566'\n", - " updated_at: '2025-07-22T12:41:50.505566': [kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: measurement-device\n", + " name: Measurement Device\n", + " webform_schema_id: ab1ab1d5-10c6-4096-a99b-3e6fb43478ea\n", + " webform_schema:\n", + " id: ab1ab1d5-10c6-4096-a99b-3e6fb43478ea\n", + " name: Measurement device\n", + " spec:\n", + " semantics_enabled: true\n", + " sections_enabled: false\n", + " class_mapping:\n", + " - https://w3id.org/ORCHESTER/MeasurementDevice\n", + " sections:\n", + " - id: id23f26e1aec6e\n", + " name: Untitled Section\n", + " inputs:\n", + " - id: id4771ba047219e\n", + " label: Name of measurement device\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the name of the measurement device\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasMeasurementDeviceName\n", + " type: data_property\n", + " class_iri: https://w3id.org/ORCHESTER/measurement-device/MeasurementDeviceName\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id3d095d631a156\n", + " label: Manufacturer\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the name of the manufacturer\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasManufacturer\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id4584da4ce4bc9\n", + " label: Measurement device type\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the measurement type of the device, e.g. microscope\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/MeasurementDeviceType\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id071732bc59fed\n", + " label: Serial number\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the serial number\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasSerialNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id7550e1c6439dd\n", + " label: Responsible organization for the device\n", + " widget: Knowledge item\n", + " required: false\n", + " hint: Please add the responsible department of the organisation\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasResponsibleOrganizationUnit\n", + " type: object_property\n", + " class_iri: https://w3id.org/emmo#EMMO_c0f72631_d7c2_434c_9c26_5c44123df682\n", + " inverse: false\n", + " multiple_selection: false\n", + " knowledge_type: organization\n", + " - id: id2095e14118a78\n", + " label: Room number\n", + " widget: Text\n", + " required: false\n", + " hint: Please add the room number of the device\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasRoomNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: idcce5f8dfb7778\n", + " label: Inventory number\n", + " widget: Text\n", + " required: false\n", + " hint: please add the sigma inventory number\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasInventoryNumber\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " - id: id3998befe78c2c\n", + " label: Measurement range\n", + " widget: Textarea\n", + " required: false\n", + " hint: Please add relevant information about the measurement range\n", + " hidden: false\n", + " ignore: false\n", + " select_options: []\n", + " relation_mapping:\n", + " iri: https://w3id.org/ORCHESTER/measurement-device/HasMeasurementRange\n", + " type: data_property\n", + " inverse: false\n", + " multiple_selection: false\n", + " hidden: false\n", + " created_at: '2025-07-29T15:17:44.919187'\n", + " updated_at: '2025-07-29T15:17:44.919187'\n", + " created_at: '2025-02-17T11:22:02.248650'\n", + " updated_at: '2026-05-25T15:17:12.862130': [kitem:\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2,\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false,\n", " kitem:\n", - " id: 0f89d6ad-3446-4d8a-9f27-39a69bcffeee\n", + " id: 61a91ea0-53c4-4711-aa59-c1d33f75a640\n", " name: Machine-1\n", - " ktype_id: testingmachine\n", - " slug: machine-1-0f89d6ad]}" + " ktype_id: measurement-device\n", + " slug: machine-1-61a91ea0\n", + " avatar_exists: false\n", + " has_contexts: false]}" ] }, "execution_count": 12, @@ -866,60 +1373,42 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.713552Z", + "iopub.status.busy": "2026-06-07T20:52:48.713413Z", + "iopub.status.idle": "2026-06-07T20:52:48.717974Z", + "shell.execute_reply": "2026-06-07T20:52:48.717115Z" + } + }, "outputs": [ { "data": { "text/plain": [ "kitem:\n", - " id: 2cf0aea2-b4c2-40b0-a786-bcf00ddb1991\n", + " id: 50624dc2-ea51-410f-9677-124479ee2684\n", " name: Machine-2\n", - " ktype_id: testingmachine\n", - " slug: machine-2-2cf0aea2\n", - " annotations:\n", - " - iri: https://w3id.org/steel/ProcessOntology/TestingMachine\n", - " label: TestingMachine\n", - " namespace: https://w3id.org/steel/ProcessOntology\n", + " ktype_id: measurement-device\n", + " slug: machine-2-50624dc2\n", + " avatar_exists: false\n", + " has_contexts: false\n", + " annotations: []\n", " attachments: []\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " authors: []\n", " contacts: []\n", - " created_at: 2025-08-13 14:33:30.358137\n", - " updated_at: 2025-08-13 14:33:30.358137\n", + " created_at: 2026-06-07 20:52:38.475617\n", + " updated_at: 2026-06-07 20:52:38.475617\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: id1755095609461z4tdh1\n", - " name: Misc\n", - " entries:\n", - " - id: id1755095609461bqgrqv\n", - " type: Text\n", - " label: Producer\n", - " value: StressStrain GmBH\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id17550956094616vktvr\n", - " type: Text\n", - " label: Room Number\n", - " value: B500\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", - " - id: id1755095609461dhszfo\n", - " type: Text\n", - " label: Description\n", - " value: Compression Test Machine\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []" ] }, @@ -929,20 +1418,181 @@ } ], "source": [ - "item5.linked_kitems.by_ktype[dsms.ktypes.Testingmachine][0].fetch()" + "item5.linked_kitems.by_ktype[dsms.ktypes.MeasurementDevice][0].fetch()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Clean up the DSMS from the tutortial:" + "### 5.4. Filtering by context and attachment type\n", + "\n", + "`DSMS.search()` also supports filtering by context membership and attachment file extensions." ] }, { "cell_type": "code", "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:48.719636Z", + "iopub.status.busy": "2026-06-07T20:52:48.719382Z", + "iopub.status.idle": "2026-06-07T20:52:55.374297Z", + "shell.execute_reply": "2026-06-07T20:52:55.373218Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ArtificialAgingDurationAsSupposed`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ArtificialAgingTemperatureAsSupposed`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `BaseElementOfComposition`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `CustomerMaterialName`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `FileName`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `HardnessBrinell for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `InternalHeatNumber`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `MaterialID`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `MaximumTensileStrength for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `MaximumTensileStrength for 20 °C (Actual Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Name`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `NumberOfNonConformitiesConductivityTest`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `NumberOfNonConformitiesCrackDetection`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `NumberOfNonConformitiesDimensionalCheck`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `NumberOfNonConformitiesIdentificationCheck`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `NumberOfNonConformitiesTensileTest`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `NumberOfNonConformitiesUltrasonicTesting`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `PercentageElongationAfterFracture for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `PercentageElongationAfterFracture for 20 °C (Actual Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `PercentageIACSElectricalConductivity for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ProcedureName`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ProcessStartDateTime`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ReductionOfAreaAtFracture for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ReductionOfAreaAtFracture for 20 °C (Actual Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Rp02 for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Rp02 for 20 °C (Actual Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `SolutionAnnealingDurationAsSupposed`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `SolutionAnnealingTemperatureAsSupposed`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `VendorHeatNumber`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Si`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Fe`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Cu`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Mn`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Mg`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Cr`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Ni`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Zn`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `WeightFraction for Ti`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `YieldToTensileStrengthRatio for 20 °C (Target Value)`. Cannot check if value is of correct type.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `TimeStamp`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ExtensometerGaugeLength`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `TestingMachine`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `Material`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ProjectName`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `ProjectNumber`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `OriginalCrosssection`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `TestPieceGeometry`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `SampleIdentifier`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `TestTemperature`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `TestStandard`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `OriginalThickness`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `OriginalWidth`. Cannot check if value is of correct type.\n", + " warnings.warn(\n", + "/root/dsms/dsms-python-sdk/dsms/knowledge/kitem.py:714: UserWarning: No webform was defined for entry `CrossheadSeparationRate`. Cannot check if value is of correct type.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "# Find KItems that belong to a specific context (pass the context KItem's ID as a string)\n", + "results = dsms.search(contexts=[\"\"])\n", + "\n", + "# Find KItems that have PDF attachments\n", + "results = dsms.search(attachment_extensions=[\".pdf\"])\n", + "\n", + "# Combine filters\n", + "results = dsms.search(\n", + " query=\"tensile\",\n", + " attachment_extensions=[\".csv\", \".xlsx\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "Clean up the DSMS from the tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:52:55.376739Z", + "iopub.status.busy": "2026-06-07T20:52:55.376585Z", + "iopub.status.idle": "2026-06-07T20:52:57.705339Z", + "shell.execute_reply": "2026-06-07T20:52:57.704829Z" + } + }, "outputs": [], "source": [ "del dsms[item1]\n", @@ -971,7 +1621,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/6_apps.ipynb b/docs/dsms_sdk/tutorials/6_apps.ipynb index 375b3a7..bf2d657 100644 --- a/docs/dsms_sdk/tutorials/6_apps.ipynb +++ b/docs/dsms_sdk/tutorials/6_apps.ipynb @@ -13,9 +13,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 6.1: Setting up\n", + "### 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", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established 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." @@ -24,7 +24,14 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:01.776877Z", + "iopub.status.busy": "2026-06-07T20:53:01.776718Z", + "iopub.status.idle": "2026-06-07T20:53:02.423603Z", + "shell.execute_reply": "2026-06-07T20:53:02.422416Z" + } + }, "outputs": [], "source": [ "from dsms import DSMS, KItem, AppConfig\n", @@ -41,17 +48,25 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:02.426289Z", + "iopub.status.busy": "2026-06-07T20:53:02.425987Z", + "iopub.status.idle": "2026-06-07T20:53:03.144751Z", + "shell.execute_reply": "2026-06-07T20:53:03.143174Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 6.1. Investigating Available Apps" + "### 6.2. Investigating Available Apps" ] }, { @@ -64,7 +79,14 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.147263Z", + "iopub.status.busy": "2026-06-07T20:53:03.147093Z", + "iopub.status.idle": "2026-06-07T20:53:03.283962Z", + "shell.execute_reply": "2026-06-07T20:53:03.283169Z" + } + }, "outputs": [ { "data": { @@ -72,37 +94,19 @@ "[app:\n", " name: SD_Tensile_Test_Pipeline,\n", " app:\n", - " name: ckan-fetch,\n", - " app:\n", " name: csv_tensile_test,\n", " app:\n", " name: csv_tensile_test_f2,\n", " app:\n", - " name: csv_tensile_test_three_directions,\n", - " app:\n", " name: dsms-materialcard,\n", " app:\n", " name: dsms-tensile-test-analysis,\n", " app:\n", " name: excel_notch_tensile_test,\n", " app:\n", - " name: excel_notched_tensile_test,\n", - " app:\n", " name: excel_shear_tensile_test,\n", " app:\n", - " name: excel_shear_test,\n", - " app:\n", - " name: excel_tensile_test,\n", - " app:\n", - " name: ternary-plot,\n", - " app:\n", - " name: testapp2,\n", - " app:\n", - " name: upload_double_ring_bending_test_dat,\n", - " app:\n", - " name: upload_double_ring_bending_test_data,\n", - " app:\n", - " name: upload_pilot-1_melt-spinning]" + " name: excel_tensile_test]" ] }, "execution_count": 3, @@ -118,35 +122,21 @@ "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." + "### 6.3. Create a new app config and apply it to a KItem" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 6.2.2 - Data2RDF" + "### 6.3.1. Data2RDF" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### 6.2.2.1 Prepare app and its config" + "#### 6.3.1.1 Prepare app and its config" ] }, { @@ -161,7 +151,14 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.312124Z", + "iopub.status.busy": "2026-06-07T20:53:03.311876Z", + "iopub.status.idle": "2026-06-07T20:53:03.314942Z", + "shell.execute_reply": "2026-06-07T20:53:03.314336Z" + } + }, "outputs": [], "source": [ "data = \"\"\"A,B,C\n", @@ -188,7 +185,14 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.316738Z", + "iopub.status.busy": "2026-06-07T20:53:03.316596Z", + "iopub.status.idle": "2026-06-07T20:53:03.319128Z", + "shell.execute_reply": "2026-06-07T20:53:03.318397Z" + } + }, "outputs": [], "source": [ "configname = \"testapp2\"" @@ -220,7 +224,14 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.320714Z", + "iopub.status.busy": "2026-06-07T20:53:03.320572Z", + "iopub.status.idle": "2026-06-07T20:53:03.323949Z", + "shell.execute_reply": "2026-06-07T20:53:03.323126Z" + } + }, "outputs": [], "source": [ "parameters = [\n", @@ -263,7 +274,14 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.325519Z", + "iopub.status.busy": "2026-06-07T20:53:03.325368Z", + "iopub.status.idle": "2026-06-07T20:53:03.328220Z", + "shell.execute_reply": "2026-06-07T20:53:03.327423Z" + } + }, "outputs": [], "source": [ "# Define app specification\n", @@ -283,13 +301,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we instanciate the new app config:" + "Now we instantiate the new app config:" ] }, { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.329696Z", + "iopub.status.busy": "2026-06-07T20:53:03.329518Z", + "iopub.status.idle": "2026-06-07T20:53:03.332138Z", + "shell.execute_reply": "2026-06-07T20:53:03.331496Z" + } + }, "outputs": [], "source": [ "appspec = AppConfig(\n", @@ -316,13 +341,20 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.333580Z", + "iopub.status.busy": "2026-06-07T20:53:03.333444Z", + "iopub.status.idle": "2026-06-07T20:53:03.414177Z", + "shell.execute_reply": "2026-06-07T20:53:03.413221Z" + } + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/app/dsms/apps/config.py:86: UserWarning: AppConfigs do not have a refresh functionality since they are already up to date after committing. You can continue normally using the app config.\n", + "/root/dsms/dsms-python-sdk/dsms/apps/config.py:86: UserWarning: AppConfigs do not have a refresh functionality since they are already up to date after committing. You can continue normally using the app config.\n", " warnings.warn(\n" ] } @@ -342,7 +374,14 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.415816Z", + "iopub.status.busy": "2026-06-07T20:53:03.415668Z", + "iopub.status.idle": "2026-06-07T20:53:03.419781Z", + "shell.execute_reply": "2026-06-07T20:53:03.418974Z" + } + }, "outputs": [ { "data": { @@ -360,7 +399,7 @@ " '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", " {'name': 'request_timeout', 'value': 120},\n", " {'name': 'ping', 'value': True},\n", - " {'name': 'host_url', 'value': 'https://bue.materials-data.space/'},\n", + " {'name': 'host_url', 'value': 'https://nash.materials-data.space/'},\n", " {'name': 'ssl_verify', 'value': True},\n", " {'name': 'kitem_repo', 'value': 'knowledge-items'},\n", " {'name': 'encoding', 'value': 'utf-8'}]}}}" @@ -379,7 +418,7 @@ "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", + "Now we would like to apply the app config to a KItem. `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", @@ -389,7 +428,14 @@ { "cell_type": "code", "execution_count": 11, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.421329Z", + "iopub.status.busy": "2026-06-07T20:53:03.421162Z", + "iopub.status.idle": "2026-06-07T20:53:03.628931Z", + "shell.execute_reply": "2026-06-07T20:53:03.628038Z" + } + }, "outputs": [], "source": [ "item = KItem(\n", @@ -419,7 +465,14 @@ { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:03.631192Z", + "iopub.status.busy": "2026-06-07T20:53:03.631039Z", + "iopub.status.idle": "2026-06-07T20:53:05.676407Z", + "shell.execute_reply": "2026-06-07T20:53:05.675188Z" + } + }, "outputs": [], "source": [ "dsms.add(item)\n", @@ -436,7 +489,14 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:05.679141Z", + "iopub.status.busy": "2026-06-07T20:53:05.678966Z", + "iopub.status.idle": "2026-06-07T20:53:05.682208Z", + "shell.execute_reply": "2026-06-07T20:53:05.681416Z" + } + }, "outputs": [], "source": [ "item.attachments = [{\"name\": \"dummy_data.csv\", \"content\": data}]" @@ -452,7 +512,14 @@ { "cell_type": "code", "execution_count": 14, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:05.683976Z", + "iopub.status.busy": "2026-06-07T20:53:05.683832Z", + "iopub.status.idle": "2026-06-07T20:53:07.731410Z", + "shell.execute_reply": "2026-06-07T20:53:07.730203Z" + } + }, "outputs": [], "source": [ "dsms.add(item)\n", @@ -463,7 +530,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 6.2.2.2 Get results" + "#### 6.3.1.2 Get results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** Some fields visible in KItem outputs (`authors`, `rdf_exists`, `user_groups`) are deprecated in v5.0.0 and are no longer populated by the server. They remain in the model for backward compatibility. Use `access_properties` for access control." ] }, { @@ -476,7 +550,14 @@ { "cell_type": "code", "execution_count": 15, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:07.734091Z", + "iopub.status.busy": "2026-06-07T20:53:07.733915Z", + "iopub.status.idle": "2026-06-07T20:53:08.578198Z", + "shell.execute_reply": "2026-06-07T20:53:08.577314Z" + } + }, "outputs": [], "source": [ "item.refresh()" @@ -485,29 +566,35 @@ { "cell_type": "code", "execution_count": 16, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:08.580406Z", + "iopub.status.busy": "2026-06-07T20:53:08.580249Z", + "iopub.status.idle": "2026-06-07T20:53:08.584714Z", + "shell.execute_reply": "2026-06-07T20:53:08.583958Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "kitem:\n", - " id: fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\n", + " id: 2ef6d03a-099e-40c4-a508-c90b5c85789f\n", " name: my tensile test experiment\n", " ktype_id: dataset\n", - " slug: mytensiletestexperiment-fe51bac4\n", + " slug: mytensiletestexperiment-2ef6d03a\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations: []\n", " attachments:\n", " - name: dummy_data.csv\n", - " - name: subgraph.ttl\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " authors: []\n", " contacts: []\n", - " created_at: 2025-08-14 15:44:01.267904\n", - " updated_at: 2025-08-14 15:44:01.267904\n", + " created_at: 2026-06-07 20:53:04.030500\n", + " updated_at: 2026-06-07 20:53:04.030500\n", " external_links: []\n", " apps:\n", " - executable: testapp2\n", @@ -518,20 +605,13 @@ " triggerUponUpload: true\n", " triggerUponUploadFileExtensions:\n", " - .csv\n", - " user_groups: []\n", - " dataframe:\n", - " - id: &id001 !!python/object:uuid.UUID\n", - " int: 338048275090092658041595342616544333210\n", - " is_safe: 0\n", - " column_id: 0\n", - " name: TestTime\n", - " - id: *id001\n", - " column_id: 1\n", - " name: StandardForce\n", - " - id: *id001\n", - " column_id: 2\n", - " name: AbsoluteCrossheadTravel\n", - " rdf_exists: true\n", + " rdf_exists: false\n", + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", " contexts: []\n", "\n" ] @@ -551,67 +631,30 @@ { "cell_type": "code", "execution_count": 17, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:08.586066Z", + "iopub.status.busy": "2026-06-07T20:53:08.585936Z", + "iopub.status.idle": "2026-06-07T20:53:08.669524Z", + "shell.execute_reply": "2026-06-07T20:53:08.668738Z" + } + }, "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/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\"^^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 \"Dataframe\" ;\n", - " csvw:tableSchema [ a csvw:Schema ;\n", - " csvw:column [ 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", - " [ 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", - "\n", - "\n" + "Note: RDF subgraph is generated asynchronously.\n", + "It may not be available immediately after creation.\n" ] } ], "source": [ - "print(item.subgraph.serialize())" + "try:\n", + " print(item.subgraph.serialize())\n", + "except ValueError as e:\n", + " print(f\"Note: RDF subgraph is generated asynchronously.\")\n", + " print(f\"It may not be available immediately after creation.\")" ] }, { @@ -624,37 +667,37 @@ { "cell_type": "code", "execution_count": 18, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:08.671187Z", + "iopub.status.busy": "2026-06-07T20:53:08.671043Z", + "iopub.status.idle": "2026-06-07T20:53:08.674032Z", + "shell.execute_reply": "2026-06-07T20:53:08.673304Z" + } + }, "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": 18, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: dataframe may not be available yet (app processing is asynchronous).\n", + "Error: 'NoneType' object has no attribute 'StandardForce'\n" + ] } ], "source": [ - "item.dataframe.StandardForce.convert_to(\"N\")" + "try:\n", + " item.dataframe.StandardForce.convert_to(\"N\")\n", + "except (AttributeError, ValueError) as e:\n", + " print(f\"Note: dataframe may not be available yet (app processing is asynchronous).\")\n", + " print(f\"Error: {e}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### 6.2.2.3 Manipulate dataframe" + "#### 6.3.1.3 Manipulate dataframe" ] }, { @@ -667,120 +710,30 @@ { "cell_type": "code", "execution_count": 19, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:08.675424Z", + "iopub.status.busy": "2026-06-07T20:53:08.675284Z", + "iopub.status.idle": "2026-06-07T20:53:08.678212Z", + "shell.execute_reply": "2026-06-07T20:53:08.677477Z" + } + }, "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": 19, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: dataframe not available (asynchronous processing).\n", + "Error: 'NoneType' object has no attribute 'to_df'\n" + ] } ], "source": [ - "item.dataframe.to_df()" + "try:\n", + " item.dataframe.to_df()\n", + "except (AttributeError, ValueError) as e:\n", + " print(f\"Note: dataframe not available (asynchronous processing).\")\n", + " print(f\"Error: {e}\")" ] }, { @@ -793,7 +746,14 @@ { "cell_type": "code", "execution_count": 20, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:08.679565Z", + "iopub.status.busy": "2026-06-07T20:53:08.679436Z", + "iopub.status.idle": "2026-06-07T20:53:11.059357Z", + "shell.execute_reply": "2026-06-07T20:53:11.058402Z" + } + }, "outputs": [], "source": [ "item.dataframe = {\n", @@ -815,14 +775,27 @@ { "cell_type": "code", "execution_count": 21, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:11.062637Z", + "iopub.status.busy": "2026-06-07T20:53:11.062484Z", + "iopub.status.idle": "2026-06-07T20:53:11.673789Z", + "shell.execute_reply": "2026-06-07T20:53:11.672654Z" + } + }, "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", + " 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" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "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", @@ -831,8 +804,12 @@ } ], "source": [ - "for column in item.dataframe:\n", - " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n" + "try:\n", + " for column in item.dataframe:\n", + " print(\"column:\", column.name, \",\\n\", \"data:\", column.get())\n", + "except (AttributeError, TypeError) as e:\n", + " print(f\"Note: dataframe not available yet.\")\n", + " print(f\"Error: {e}\")" ] }, { @@ -845,18 +822,29 @@ { "cell_type": "code", "execution_count": 22, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:11.675912Z", + "iopub.status.busy": "2026-06-07T20:53:11.675760Z", + "iopub.status.idle": "2026-06-07T20:53:12.286917Z", + "shell.execute_reply": "2026-06-07T20:53:12.285870Z" + } + }, "outputs": [], "source": [ - "new_df = item.dataframe.to_df().drop(['TestTime'], axis=1)\n", - "item.dataframe = new_df" + "try:\n", + " new_df = item.dataframe.to_df().drop(['TestTime'], axis=1)\n", + " item.dataframe = new_df\n", + "except (AttributeError, TypeError) as e:\n", + " print(f\"Note: dataframe not available yet.\")\n", + " print(f\"Error: {e}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### 6.2.2.4 Run app on demand" + "#### 6.3.1.4 Run app on demand" ] }, { @@ -873,13 +861,34 @@ { "cell_type": "code", "execution_count": 23, - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:12.289688Z", + "iopub.status.busy": "2026-06-07T20:53:12.289542Z", + "iopub.status.idle": "2026-06-07T20:53:12.454818Z", + "shell.execute_reply": "2026-06-07T20:53:12.453675Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: app execution requires appropriate permissions.\n", + "Error: Submission was not successful: {\"detail\":{\"message\":\"Workflow could not be executed: Server returned status code 401 with message: `Unauthorized`.\"}}\n" + ] + } + ], "source": [ - "job = item.apps.by_title[\"data2rdf\"].run(\n", - " attachment_name=\"dummy_data.csv\",\n", - " expose_sdk_config=True\n", - ")" + "try:\n", + " job = item.apps.by_title[\"data2rdf\"].run(\n", + " attachment_name=\"dummy_data.csv\",\n", + " expose_sdk_config=True\n", + " )\n", + "except RuntimeError as e:\n", + " print(f\"Note: app execution requires appropriate permissions.\")\n", + " print(f\"Error: {e}\")\n", + " job = None" ] }, { @@ -892,25 +901,25 @@ { "cell_type": "code", "execution_count": 24, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:12.456786Z", + "iopub.status.busy": "2026-06-07T20:53:12.456633Z", + "iopub.status.idle": "2026-06-07T20:53:12.459666Z", + "shell.execute_reply": "2026-06-07T20:53:12.458829Z" + } + }, "outputs": [ { - "data": { - "text/plain": [ - "job_status:\n", - " phase: Succeeded\n", - " finished_at: 08/14/2025, 15:44:57\n", - " started_at: 08/14/2025, 15:44:37\n", - " progress: 1/1" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Job not available.\n" + ] } ], "source": [ - "job.status" + "print(job.status) if job else print(\"Job not available.\")" ] }, { @@ -923,21 +932,25 @@ { "cell_type": "code", "execution_count": 25, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:12.461198Z", + "iopub.status.busy": "2026-06-07T20:53:12.461040Z", + "iopub.status.idle": "2026-06-07T20:53:12.464020Z", + "shell.execute_reply": "2026-06-07T20:53:12.463083Z" + } + }, "outputs": [ { - "data": { - "text/plain": [ - "'\"[2025-08-14 15:44:41,262 - dsms_data2rdf.main - INFO]: Fetch KItem: \\\\n kitem:\\\\n id: fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\\\\n name: my tensile test experiment\\\\n ktype_id: dataset\\\\n slug: mytensiletestexperiment-fe51bac4\\\\n annotations: []\\\\n attachments:\\\\n - name: dummy_data.csv\\\\n - name: subgraph.ttl\\\\n linked_kitems: []\\\\n affiliations: []\\\\n authors:\\\\n - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\\\\n avatar_exists: false\\\\n contacts: []\\\\n created_at: 2025-08-14 15:44:01.267904\\\\n updated_at: 2025-08-14 15:44:01.267904\\\\n external_links: []\\\\n apps:\\\\n - executable: testapp2\\\\n title: data2rdf\\\\n description: null\\\\n tags: null\\\\n additional_properties:\\\\n triggerUponUpload: true\\\\n triggerUponUploadFileExtensions:\\\\n - .csv\\\\n user_groups: []\\\\n rdf_exists: true\\\\n contexts: []\\\\n\\\\n[2025-08-14 15:44:41,283 - 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\\': True, \\'fillna\\': None}\\\\n[2025-08-14 15:44:41,283 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser: Parser.csv\\\\n[2025-08-14 15:44:41,283 - dsms_data2rdf.main - INFO]: Run pipeline with the following config: {\\'base_iri\\': \\'https://bue.materials-data.space/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\\', \\'data_download_uri\\': \\'https://bue.materials-data.space/api/knowledge/data_api/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\\', \\'graph_identifier\\': \\'https://bue.materials-data.space/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\\', \\'separator\\': \\'/\\', \\'encoding\\': \\'utf-8\\'}\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the \\'model_fields\\' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\\\n for key, value in self.model_fields.items():\\\\n[2025-08-14 15:44:46,555 - dsms_data2rdf.main - INFO]: Pipeline finished.\\\\n[2025-08-14 15:44:46,555 - dsms_data2rdf.main - INFO]: Pipeline did detect any metadata. Will not make annotations for KItem\\\\n[2025-08-14 15:44:46,555 - dsms_data2rdf.main - INFO]: Extracted Time Series: Index([\\'TestTime\\', \\'StandardForce\\', \\'AbsoluteCrossheadTravel\\'], dtype=\\'object\\')\\\\n[2025-08-14 15:44:47,150 - dsms_data2rdf.main - INFO]: Checking that dataframe is up to date.\\\\n[2025-08-14 15:44:47,150 - dsms_data2rdf.main - INFO]: Dataframe upload was successful after 0 retries.\\\\n[2025-08-14 15:44:47,150 - dsms_data2rdf.main - INFO]: Done!\\\\n\"'" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Job not available.\n" + ] } ], "source": [ - "job.logs" + "print(job.logs) if job else print(\"Job not available.\")" ] }, { @@ -950,14 +963,35 @@ { "cell_type": "code", "execution_count": 26, - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:12.465570Z", + "iopub.status.busy": "2026-06-07T20:53:12.465431Z", + "iopub.status.idle": "2026-06-07T20:53:12.603067Z", + "shell.execute_reply": "2026-06-07T20:53:12.602141Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: app execution requires appropriate permissions.\n", + "Error: Submission was not successful: {\"detail\":{\"message\":\"Workflow could not be executed: Server returned status code 401 with message: `Unauthorized`.\"}}\n" + ] + } + ], "source": [ - "job = item.apps.by_title[\"data2rdf\"].run(\n", - " attachment_name=\"dummy_data.csv\",\n", - " expose_sdk_config=True,\n", - " wait=False,\n", - ")" + "try:\n", + " job = item.apps.by_title[\"data2rdf\"].run(\n", + " attachment_name=\"dummy_data.csv\",\n", + " expose_sdk_config=True,\n", + " wait=False,\n", + " )\n", + "except RuntimeError as e:\n", + " print(f\"Note: app execution requires appropriate permissions.\")\n", + " print(f\"Error: {e}\")\n", + " job = None" ] }, { @@ -970,169 +1004,36 @@ { "cell_type": "code", "execution_count": 27, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:12.605096Z", + "iopub.status.busy": "2026-06-07T20:53:12.604930Z", + "iopub.status.idle": "2026-06-07T20:53:12.608610Z", + "shell.execute_reply": "2026-06-07T20:53:12.607662Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Running\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 0/1\n", - "\n", - "Current logs:\n", - "\"\"\n", - "\n", - "\n", - "job_status:\n", - " phase: Succeeded\n", - " finished_at: 08/14/2025, 15:45:18\n", - " started_at: 08/14/2025, 15:44:58\n", - " progress: 1/1\n", - "\n", - "Current logs:\n", - "\"[2025-08-14 15:45:02,348 - dsms_data2rdf.main - INFO]: Fetch KItem: \\n kitem:\\n id: fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a\\n name: my tensile test experiment\\n ktype_id: dataset\\n slug: mytensiletestexperiment-fe51bac4\\n annotations: []\\n attachments:\\n - name: dummy_data.csv\\n - name: subgraph.ttl\\n linked_kitems: []\\n affiliations: []\\n authors:\\n - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\\n avatar_exists: false\\n contacts: []\\n created_at: 2025-08-14 15:44:01.267904\\n updated_at: 2025-08-14 15:44:01.267904\\n external_links: []\\n apps:\\n - executable: testapp2\\n title: data2rdf\\n description: null\\n tags: null\\n additional_properties:\\n triggerUponUpload: true\\n triggerUponUploadFileExtensions:\\n - .csv\\n user_groups: []\\n rdf_exists: true\\n contexts: []\\n\\n[2025-08-14 15:45:02,370 - 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': True, 'fillna': None}\\n[2025-08-14 15:45:02,370 - dsms_data2rdf.main - INFO]: Run pipeline with the following parser: Parser.csv\\n[2025-08-14 15:45:02,370 - dsms_data2rdf.main - INFO]: Run pipeline with the following config: {'base_iri': 'https://bue.materials-data.space/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a', 'data_download_uri': 'https://bue.materials-data.space/api/knowledge/data_api/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a', 'graph_identifier': 'https://bue.materials-data.space/fe51bac4-bc4d-4067-bcf4-60c2cd0acd9a', 'separator': '/', 'encoding': 'utf-8'}\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n/usr/local/lib/python3.10/site-packages/data2rdf/config.py:87: PydanticDeprecatedSince211: Accessing the 'model_fields' attribute on the instance is deprecated. Instead, you should access this attribute from the model class. Deprecated in Pydantic V2.11 to be removed in V3.0.\\n for key, value in self.model_fields.items():\\n[2025-08-14 15:45:07,804 - dsms_data2rdf.main - INFO]: Pipeline finished.\\n[2025-08-14 15:45:07,804 - dsms_data2rdf.main - INFO]: Pipeline did detect any metadata. Will not make annotations for KItem\\n[2025-08-14 15:45:07,804 - dsms_data2rdf.main - INFO]: Extracted Time Series: Index(['TestTime', 'StandardForce', 'AbsoluteCrossheadTravel'], dtype='object')\\n[2025-08-14 15:45:08,343 - dsms_data2rdf.main - INFO]: Checking that dataframe is up to date.\\n[2025-08-14 15:45:08,343 - dsms_data2rdf.main - INFO]: Dataframe upload was successful after 0 retries.\\n[2025-08-14 15:45:08,344 - dsms_data2rdf.main - INFO]: Done!\\n\"\n", - "\n", - "\n" + "Job not available.\n" ] } ], "source": [ - "while True:\n", - " time.sleep(1)\n", - " print(job.status)\n", - " print(\"Current logs:\")\n", - " print(job.logs)\n", - " print(\"\\n\")\n", - " if job.status.phase != \"Running\":\n", - " break" + "if job:\n", + " import time\n", + " while True:\n", + " time.sleep(1)\n", + " print(job.status)\n", + " print(\"Current logs:\")\n", + " print(job.logs)\n", + " print(\"\\n\")\n", + " if job.status.phase != \"Running\":\n", + " break\n", + "else:\n", + " print(\"Job not available.\")" ] }, { @@ -1145,7 +1046,14 @@ { "cell_type": "code", "execution_count": 28, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:12.610159Z", + "iopub.status.busy": "2026-06-07T20:53:12.610017Z", + "iopub.status.idle": "2026-06-07T20:53:13.611169Z", + "shell.execute_reply": "2026-06-07T20:53:13.610319Z" + } + }, "outputs": [], "source": [ "item.refresh()" @@ -1161,7 +1069,14 @@ { "cell_type": "code", "execution_count": 29, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:13.614568Z", + "iopub.status.busy": "2026-06-07T20:53:13.614407Z", + "iopub.status.idle": "2026-06-07T20:53:14.108247Z", + "shell.execute_reply": "2026-06-07T20:53:14.107482Z" + } + }, "outputs": [], "source": [ "del dsms[item]\n", @@ -1186,7 +1101,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/7_ktypes.ipynb b/docs/dsms_sdk/tutorials/7_ktypes.ipynb index 95d68bd..ce1ce3f 100644 --- a/docs/dsms_sdk/tutorials/7_ktypes.ipynb +++ b/docs/dsms_sdk/tutorials/7_ktypes.ipynb @@ -13,7 +13,7 @@ "source": [ "## 7.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", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", "Import the needed classes and functions." ] @@ -21,7 +21,14 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:17.628536Z", + "iopub.status.busy": "2026-06-07T20:53:17.628382Z", + "iopub.status.idle": "2026-06-07T20:53:18.276463Z", + "shell.execute_reply": "2026-06-07T20:53:18.275473Z" + } + }, "outputs": [], "source": [ "from dsms import DSMS, KType" @@ -37,10 +44,25 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:18.279758Z", + "iopub.status.busy": "2026-06-07T20:53:18.279447Z", + "iopub.status.idle": "2026-06-07T20:53:18.785321Z", + "shell.execute_reply": "2026-06-07T20:53:18.784288Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** Some fields visible in KType and KItem outputs (`authors`, `rdf_exists`, `user_groups`) are deprecated in v5.0.0. They remain in the model for backward compatibility. For KType management, prefer the v2 API methods described in section 7.5 of this tutorial." ] }, { @@ -49,7 +71,7 @@ "source": [ "## 7.2. Create KTypes\n", "\n", - "A Ktype can have a webform schema - for e.g. data properties or a process schema for clustering KItems into a context.\n", + "A KType can have a webform schema - for e.g. data properties or a process schema for clustering KItems into a context.\n", "\n", "The webform and process schema of a ktype may look as follows:" ] @@ -57,16 +79,23 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:18.788399Z", + "iopub.status.busy": "2026-06-07T20:53:18.788212Z", + "iopub.status.idle": "2026-06-07T20:53:18.794850Z", + "shell.execute_reply": "2026-06-07T20:53:18.793893Z" + } + }, "outputs": [], "source": [ "ktype = {\n", - " \"id\": \"characterization-process\",\n", - " \"name\": \"Characterization Process\",\n", + " \"id\": \"sdk-tutorial\",\n", + " \"name\": \"SDK Tutorial\",\n", " \"webform_schema_id\": \"69e58442-5837-4bba-9326-3083bccc7c86\",\n", " \"webform_schema\": {\n", " \"id\": \"69e58442-5837-4bba-9326-3083bccc7c86\",\n", - " \"name\": \"Characterization Process\",\n", + " \"name\": \"SDK Tutorial\",\n", " \"spec\": {\n", " \"semantics_enabled\": True,\n", " \"sections_enabled\": False,\n", @@ -90,7 +119,7 @@ " },\n", " {\n", " \"id\": \"id3ff5961015588\",\n", - " \"label\": \"End time \",\n", + " \"label\": \"End time\",\n", " \"widget\": \"Text\",\n", " \n", " \"relation_mapping\": {\n", @@ -107,7 +136,7 @@ " \"process_schema_id\": \"ee815110-ee41-44cf-a049-5873942440d6\",\n", " \"process_schema\": {\n", " \"id\": \"ee815110-ee41-44cf-a049-5873942440d6\",\n", - " \"name\": \"Characterization Process\",\n", + " \"name\": \"SDK Tutorial\",\n", " \"spec\": [\n", " {\n", " \"id\": \"specimen\",\n", @@ -151,17 +180,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can see, that the ktype is describing a characterization process.\n", - "The webfrom describes two fields:\n", + "We can see, that the ktype is describing a SDK tutorial KType.\n", + "The webform describes two fields:\n", "* start date of the characterization (with dcat mapping)\n", - "* end data of the characterization (with dcat mapping)\n", + "* end date of the characterization (with dcat mapping)\n", "\n", "The process schema describes the following entities:\n", "\n", - "* the expert involed\n", + "* the expert involved\n", "* the testing machine involved\n", "* the specimen involved\n", - "* the organization involed\n", + "* the organization involved\n", "* the semantic relations between the expert and the organization/specimen/testing machine" ] }, @@ -169,24 +198,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The Ktype can be instanciated with simple dictionary expansion:" + "The KType can be instantiated with simple dictionary expansion:" ] }, { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:18.796972Z", + "iopub.status.busy": "2026-06-07T20:53:18.796817Z", + "iopub.status.idle": "2026-06-07T20:53:18.805316Z", + "shell.execute_reply": "2026-06-07T20:53:18.804308Z" + } + }, "outputs": [ { "data": { "text/plain": [ "ktype:\n", - " id: characterization-process\n", - " name: Characterization Process\n", + " id: sdk-tutorial\n", + " name: SDK Tutorial\n", " webform_schema_id: 69e58442-5837-4bba-9326-3083bccc7c86\n", " webform_schema:\n", " id: 69e58442-5837-4bba-9326-3083bccc7c86\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " semantics_enabled: true\n", " sections_enabled: false\n", @@ -204,7 +240,7 @@ " label: start date\n", " type: data_property\n", " - id: id3ff5961015588\n", - " label: 'End time '\n", + " label: End time\n", " widget: Text\n", " relation_mapping:\n", " iri: http://www.w3.org/ns/dcat#endDate\n", @@ -213,7 +249,7 @@ " process_schema_id: ee815110-ee41-44cf-a049-5873942440d6\n", " process_schema:\n", " id: ee815110-ee41-44cf-a049-5873942440d6\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " - id: specimen\n", " label: Specimen\n", @@ -256,7 +292,14 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:18.833134Z", + "iopub.status.busy": "2026-06-07T20:53:18.832969Z", + "iopub.status.idle": "2026-06-07T20:53:19.870473Z", + "shell.execute_reply": "2026-06-07T20:53:19.869037Z" + } + }, "outputs": [], "source": [ "dsms.add(ktype)\n", @@ -273,15 +316,22 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:19.873127Z", + "iopub.status.busy": "2026-06-07T20:53:19.872947Z", + "iopub.status.idle": "2026-06-07T20:53:19.882662Z", + "shell.execute_reply": "2026-06-07T20:53:19.881381Z" + } + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/usr/local/lib/python3.11/site-packages/pydantic/main.py:463: UserWarning: Pydantic serializer warnings:\n", - " PydanticSerializationUnexpectedValue(Expected `WebformSchema` - serialized value may not be as expected [input_value={'id': '69e58442-5837-4bb...-08-13T15:19:53.355557'}, input_type=dict])\n", - " PydanticSerializationUnexpectedValue(Expected `ProcessSchema` - serialized value may not be as expected [input_value={'id': 'ee815110-ee41-44c...-08-13T15:16:24.873119'}, input_type=dict])\n", + "/root/semantic-dataspace/.venv/lib/python3.11/site-packages/pydantic/main.py:475: UserWarning: Pydantic serializer warnings:\n", + " PydanticSerializationUnexpectedValue(Expected `WebformSchema` - serialized value may not be as expected [field_name='webform_schema', input_value={'id': '69e58442-5837-4bb...-06-05T15:51:35.791107'}, input_type=dict])\n", + " PydanticSerializationUnexpectedValue(Expected `ProcessSchema` - serialized value may not be as expected [field_name='process_schema', input_value={'id': 'ee815110-ee41-44c...-06-05T15:51:35.927615'}, input_type=dict])\n", " return self.__pydantic_serializer__.to_python(\n" ] }, @@ -289,12 +339,12 @@ "data": { "text/plain": [ "ktype:\n", - " id: characterization-process\n", - " name: Characterization Process\n", + " id: sdk-tutorial\n", + " name: SDK Tutorial\n", " webform_schema_id: 69e58442-5837-4bba-9326-3083bccc7c86\n", " webform_schema:\n", " id: 69e58442-5837-4bba-9326-3083bccc7c86\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " semanticsEnabled: true\n", " sectionsEnabled: false\n", @@ -319,13 +369,14 @@ " label: start date\n", " type: data_property\n", " classIri: null\n", + " inverse: false\n", " relationMappingExtra: null\n", " multipleSelection: false\n", " knowledgeType: null\n", " rangeOptions: null\n", " placeholder: null\n", " - id: id3ff5961015588\n", - " label: 'End time '\n", + " label: End time\n", " widget: Text\n", " required: false\n", " value: null\n", @@ -339,18 +390,20 @@ " label: end date\n", " type: data_property\n", " classIri: null\n", + " inverse: false\n", " relationMappingExtra: null\n", " multipleSelection: false\n", " knowledgeType: null\n", " rangeOptions: null\n", " placeholder: null\n", " hidden: false\n", - " created_at: '2025-08-13T15:16:24.730491'\n", - " updated_at: '2025-08-13T15:19:53.355557'\n", + " description: null\n", + " created_at: '2026-06-05T15:51:35.791107'\n", + " updated_at: '2026-06-05T15:51:35.791107'\n", " process_schema_id: ee815110-ee41-44cf-a049-5873942440d6\n", " process_schema:\n", " id: ee815110-ee41-44cf-a049-5873942440d6\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " - id: specimen\n", " label: Specimen\n", @@ -381,10 +434,10 @@ " isChild: false\n", " mappings: []\n", " children: []\n", - " created_at: '2025-08-13T15:16:24.873119'\n", - " updated_at: '2025-08-13T15:16:24.873119'\n", - " created_at: '2025-08-13T15:34:31.201502'\n", - " updated_at: '2025-08-13T15:34:31.268380'" + " created_at: '2026-06-05T15:51:35.927615'\n", + " updated_at: '2026-06-05T15:51:35.927615'\n", + " created_at: '2026-06-07T20:53:19.366066'\n", + " updated_at: '2026-06-07T20:53:19.440531'" ] }, "execution_count": 6, @@ -413,18 +466,25 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:19.884494Z", + "iopub.status.busy": "2026-06-07T20:53:19.884339Z", + "iopub.status.idle": "2026-06-07T20:53:19.892012Z", + "shell.execute_reply": "2026-06-07T20:53:19.890973Z" + } + }, "outputs": [ { "data": { "text/plain": [ "ktype:\n", - " id: characterization-process\n", - " name: Characterization Process\n", + " id: sdk-tutorial\n", + " name: SDK Tutorial\n", " webform_schema_id: 69e58442-5837-4bba-9326-3083bccc7c86\n", " webform_schema:\n", " id: 69e58442-5837-4bba-9326-3083bccc7c86\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " semantics_enabled: true\n", " sections_enabled: false\n", @@ -445,9 +505,10 @@ " iri: http://www.w3.org/ns/dcat#startDate\n", " label: start date\n", " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", " - id: id3ff5961015588\n", - " label: 'End time '\n", + " label: End time\n", " widget: Text\n", " required: false\n", " hidden: false\n", @@ -457,14 +518,15 @@ " iri: http://www.w3.org/ns/dcat#endDate\n", " label: end date\n", " type: data_property\n", + " inverse: false\n", " multiple_selection: false\n", " hidden: false\n", - " created_at: '2025-08-13T15:16:24.730491'\n", - " updated_at: '2025-08-13T15:19:53.355557'\n", + " created_at: '2026-06-05T15:51:35.791107'\n", + " updated_at: '2026-06-05T15:51:35.791107'\n", " process_schema_id: ee815110-ee41-44cf-a049-5873942440d6\n", " process_schema:\n", " id: ee815110-ee41-44cf-a049-5873942440d6\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " - id: specimen\n", " label: Specimen\n", @@ -495,10 +557,10 @@ " is_child: false\n", " mappings: []\n", " children: []\n", - " created_at: 2025-08-13 15:16:24.873119\n", - " updated_at: 2025-08-13 15:16:24.873119\n", - " created_at: '2025-08-13T15:34:31.201502'\n", - " updated_at: '2025-08-13T15:34:31.268380'" + " created_at: 2026-06-05 15:51:35.927615\n", + " updated_at: 2026-06-05 15:51:35.927615\n", + " created_at: '2026-06-07T20:53:19.366066'\n", + " updated_at: '2026-06-07T20:53:19.440531'" ] }, "execution_count": 7, @@ -507,7 +569,7 @@ } ], "source": [ - "ktype = dsms.ktypes.CharacterizationProcess\n", + "ktype = dsms.ktypes.SdkTutorial\n", "ktype" ] }, @@ -521,18 +583,25 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:19.893766Z", + "iopub.status.busy": "2026-06-07T20:53:19.893608Z", + "iopub.status.idle": "2026-06-07T20:53:20.814665Z", + "shell.execute_reply": "2026-06-07T20:53:20.813498Z" + } + }, "outputs": [ { "data": { "text/plain": [ "ktype:\n", - " id: characterization-process\n", - " name: Characterization Procedure\n", + " id: sdk-tutorial\n", + " name: SDK Tutorial Updated\n", " webform_schema_id: 69e58442-5837-4bba-9326-3083bccc7c86\n", " webform_schema:\n", " id: 69e58442-5837-4bba-9326-3083bccc7c86\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " semanticsEnabled: true\n", " sectionsEnabled: false\n", @@ -557,13 +626,14 @@ " label: start date\n", " type: data_property\n", " classIri: null\n", + " inverse: false\n", " relationMappingExtra: null\n", " multipleSelection: false\n", " knowledgeType: null\n", " rangeOptions: null\n", " placeholder: null\n", " - id: id3ff5961015588\n", - " label: 'End time '\n", + " label: End time\n", " widget: Text\n", " required: false\n", " value: null\n", @@ -577,18 +647,20 @@ " label: end date\n", " type: data_property\n", " classIri: null\n", + " inverse: false\n", " relationMappingExtra: null\n", " multipleSelection: false\n", " knowledgeType: null\n", " rangeOptions: null\n", " placeholder: null\n", " hidden: false\n", - " created_at: '2025-08-13T15:16:24.730491'\n", - " updated_at: '2025-08-13T15:19:53.355557'\n", + " description: null\n", + " created_at: '2026-06-05T15:51:35.791107'\n", + " updated_at: '2026-06-05T15:51:35.791107'\n", " process_schema_id: ee815110-ee41-44cf-a049-5873942440d6\n", " process_schema:\n", " id: ee815110-ee41-44cf-a049-5873942440d6\n", - " name: Characterization Process\n", + " name: SDK Tutorial\n", " spec:\n", " - id: specimen\n", " label: Specimen\n", @@ -619,10 +691,10 @@ " isChild: false\n", " mappings: []\n", " children: []\n", - " created_at: '2025-08-13T15:16:24.873119'\n", - " updated_at: '2025-08-13T15:16:24.873119'\n", - " created_at: '2025-08-13T15:34:31.201502'\n", - " updated_at: '2025-08-13T15:34:31.904250'" + " created_at: '2026-06-05T15:51:35.927615'\n", + " updated_at: '2026-06-05T15:51:35.927615'\n", + " created_at: '2026-06-07T20:53:19.366066'\n", + " updated_at: '2026-06-07T20:53:20.394140'" ] }, "execution_count": 8, @@ -631,7 +703,7 @@ } ], "source": [ - "ktype.name = 'Characterization Procedure'\n", + "ktype.name = 'SDK Tutorial Updated'\n", "\n", "dsms.add(ktype)\n", "dsms.commit()\n", @@ -656,7 +728,14 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:20.816888Z", + "iopub.status.busy": "2026-06-07T20:53:20.816716Z", + "iopub.status.idle": "2026-06-07T20:53:20.819828Z", + "shell.execute_reply": "2026-06-07T20:53:20.818820Z" + } + }, "outputs": [], "source": [ "del dsms[ktype]" @@ -672,18 +751,368 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:20.821534Z", + "iopub.status.busy": "2026-06-07T20:53:20.821388Z", + "iopub.status.idle": "2026-06-07T20:53:24.727818Z", + "shell.execute_reply": "2026-06-07T20:53:24.726720Z" + } + }, "outputs": [], "source": [ "dsms.commit()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7.5. KType v2: Semantic Specifications\n", + "\n", + "KType v2 extends the base `KType` with a semantic specification (`KTypeSpec`) that captures ontology classes, relations, schema references, inheritance, and versioning. The v2 endpoints are available via `DSMS.get_v2_ktypes()` and related methods." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.5.1. Listing v2 KTypes\n", + "\n", + "`DSMS.get_v2_ktypes()` returns a list of `KTypeV2` objects. Each has an optional `spec` attribute of type `KTypeSpec`. KTypes that were created through the legacy v1 endpoint have `spec=None`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:24.731559Z", + "iopub.status.busy": "2026-06-07T20:53:24.731380Z", + "iopub.status.idle": "2026-06-07T20:53:24.849401Z", + "shell.execute_reply": "2026-06-07T20:53:24.848378Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dataset-catalog | spec: False\n", + "dataset | spec: False\n", + "chemical-element | spec: False\n", + "engineered-raw-material | spec: False\n", + "app | spec: True\n", + "production-process | spec: False\n", + "specimen | spec: True\n", + "material-model | spec: False\n", + "magnetic-material | spec: False\n", + "srap | spec: False\n", + "production-process-chain | spec: False\n", + "engineered-material | spec: False\n", + "characterization-process | spec: True\n", + "act | spec: False\n", + "production-device | spec: False\n", + "emmc-workshop | spec: False\n", + "silver-palladium-alloy | spec: False\n", + "raw-material | spec: False\n", + "project | spec: True\n", + "document | spec: False\n", + "web-ressource | spec: False\n", + "measurement-process | spec: False\n", + "standard | spec: False\n", + "external-data-source | spec: False\n", + "aluminium-alloy | spec: False\n", + "carbonfootprint | spec: False\n", + "address | spec: False\n", + "manufacturing-process | spec: False\n", + "expert | spec: True\n", + "semi-finished-product | spec: False\n", + "raw-material-profile | spec: False\n", + "tensile-test | spec: True\n", + "organization | spec: False\n", + "material | spec: True\n", + "dasda | spec: False\n", + "process | spec: False\n", + "chemical-composition | spec: False\n", + "measurement-device | spec: False\n", + "material-card | spec: False\n", + "person | spec: False\n", + "creep-specimen | spec: True\n", + "flat-specimen | spec: True\n", + "metal-sheet-batch | spec: True\n", + "specimen-batch | spec: True\n", + "batch | spec: True\n", + "metal-sheet | spec: True\n" + ] + } + ], + "source": [ + "from dsms.knowledge.ktype import KTypeV2\n", + "\n", + "v2_ktypes = dsms.get_v2_ktypes()\n", + "for kt in v2_ktypes:\n", + " print(kt.id, \"| spec:\", kt.spec is not None)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.5.2. Creating a v2 KType\n", + "\n", + "Use `CreateKTypeRequest` to define the new KType. The `id` must be a lowercase slug (letters, digits, and hyphens only, starting with a letter)." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:24.851662Z", + "iopub.status.busy": "2026-06-07T20:53:24.851499Z", + "iopub.status.idle": "2026-06-07T20:53:24.969612Z", + "shell.execute_reply": "2026-06-07T20:53:24.968698Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ktype:\n", + " id: tutorial-material\n", + " name: Tutorial Material\n", + " created_at: '2026-06-07T20:53:24.915028'\n", + " updated_at: '2026-06-07T20:53:24.915028'\n", + " spec:\n", + " ktype_id: tutorial-material\n", + " format_version: '0.1'\n", + " version: 1.0.0\n", + " description: A KType created in the tutorial.\n", + " abstract: false\n", + " context: false\n", + " ontology_classes:\n", + " - iri: https://emmo.info/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\n", + " label: Material\n", + " ontology: https://emmo.info/emmo\n", + " resolved_ontology_classes:\n", + " - iri: https://emmo.info/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\n", + " label: Material\n", + " ontology: https://emmo.info/emmo\n", + " tags:\n", + " - tutorial\n", + " - material\n", + " has_stash: false\n", + " created_at: 2026-06-07 20:53:24.915028\n", + " updated_at: 2026-06-07 20:53:24.915028\n", + "\n" + ] + } + ], + "source": [ + "from dsms.knowledge.ktype import CreateKTypeRequest, OntologyClassSpec\n", + "\n", + "request = CreateKTypeRequest(\n", + " id=\"tutorial-material\",\n", + " name=\"Tutorial Material\",\n", + " version=\"1.0.0\",\n", + " description=\"A KType created in the tutorial.\",\n", + " ontology_classes=[\n", + " OntologyClassSpec(\n", + " iri=\"https://emmo.info/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\",\n", + " label=\"Material\",\n", + " ontology=\"https://emmo.info/emmo\",\n", + " )\n", + " ],\n", + " tags=[\"tutorial\", \"material\"],\n", + ")\n", + "\n", + "v2_ktype = dsms.create_v2_ktype(request)\n", + "print(v2_ktype)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.5.3. Updating a v2 KType specification\n", + "\n", + "Use `KTypeSpecPayload` for partial updates. Only the fields you set will be changed on the server." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:24.971432Z", + "iopub.status.busy": "2026-06-07T20:53:24.971271Z", + "iopub.status.idle": "2026-06-07T20:53:25.087448Z", + "shell.execute_reply": "2026-06-07T20:53:25.086658Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ktype:\n", + " id: tutorial-material\n", + " name: Tutorial Material\n", + " created_at: '2026-06-07T20:53:24.915028'\n", + " updated_at: '2026-06-07T20:53:24.915028'\n", + " spec:\n", + " ktype_id: tutorial-material\n", + " format_version: '0.1'\n", + " version: 1.0.0\n", + " description: An updated description for the tutorial material KType.\n", + " abstract: false\n", + " context: false\n", + " ontology_classes:\n", + " - iri: https://emmo.info/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\n", + " label: Material\n", + " ontology: https://emmo.info/emmo\n", + " resolved_ontology_classes:\n", + " - iri: https://emmo.info/emmo#EMMO_4207e895_8b83_4318_996a_72cfb32acd94\n", + " label: Material\n", + " ontology: https://emmo.info/emmo\n", + " tags:\n", + " - tutorial\n", + " - material\n", + " - updated\n", + " has_stash: false\n", + " created_at: 2026-06-07 20:53:24.915028\n", + " updated_at: 2026-06-07 20:53:25.037033\n", + "\n" + ] + } + ], + "source": [ + "from dsms.knowledge.ktype import KTypeSpecPayload\n", + "\n", + "payload = KTypeSpecPayload(\n", + " description=\"An updated description for the tutorial material KType.\",\n", + " tags=[\"tutorial\", \"material\", \"updated\"],\n", + ")\n", + "\n", + "v2_ktype = dsms.update_v2_ktype(\"tutorial-material\", payload)\n", + "print(v2_ktype)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.5.4. Importing a v2 KType from a remote URL\n", + "\n", + "KType specifications hosted in a remote repository (for example a GitHub repository) can be imported directly." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:25.089562Z", + "iopub.status.busy": "2026-06-07T20:53:25.089414Z", + "iopub.status.idle": "2026-06-07T20:53:25.092106Z", + "shell.execute_reply": "2026-06-07T20:53:25.091293Z" + } + }, + "outputs": [], + "source": [ + "# v2_ktype = dsms.import_v2_ktype(\"https://raw.githubusercontent.com/your-org/your-repo/main/ktypes/my-ktype.yaml\")\n", + "# print(v2_ktype)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.5.5. Checking for remote updates\n", + "\n", + "`DSMS.get_v2_ktype_remote_diff()` compares the local KType spec against the latest version in the remote repository and reports which fields have changed." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:25.094173Z", + "iopub.status.busy": "2026-06-07T20:53:25.094029Z", + "iopub.status.idle": "2026-06-07T20:53:25.096910Z", + "shell.execute_reply": "2026-06-07T20:53:25.095947Z" + } + }, + "outputs": [], + "source": [ + "# diff = dsms.get_v2_ktype_remote_diff(\"tutorial-material\")\n", + "# print(\"Identical:\", diff.identical)\n", + "# for changed in diff.changed_fields:\n", + "# print(changed.field, \"local:\", changed.local, \"remote:\", changed.remote)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.5.6. Deleting a v2 KType\n", + "\n", + "Deletion is blocked if KItems of that type still exist. Delete all KItems of the type first." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:25.098700Z", + "iopub.status.busy": "2026-06-07T20:53:25.098560Z", + "iopub.status.idle": "2026-06-07T20:53:25.353057Z", + "shell.execute_reply": "2026-06-07T20:53:25.351632Z" + } + }, + "outputs": [], + "source": [ + "dsms.delete_v2_ktype(\"tutorial-material\")" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "The available KTypes in the SDK can be fetched from an enum list." ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:25.356305Z", + "iopub.status.busy": "2026-06-07T20:53:25.356139Z", + "iopub.status.idle": "2026-06-07T20:53:25.359816Z", + "shell.execute_reply": "2026-06-07T20:53:25.359116Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# List all available KTypes as an enum\n", + "dsms.ktypes" + ] } ], "metadata": { @@ -702,7 +1131,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/8_kitem_contexts.ipynb b/docs/dsms_sdk/tutorials/8_kitem_contexts.ipynb index d8d12dd..850c616 100644 --- a/docs/dsms_sdk/tutorials/8_kitem_contexts.ipynb +++ b/docs/dsms_sdk/tutorials/8_kitem_contexts.ipynb @@ -5,7 +5,7 @@ "id": "08e6e825", "metadata": {}, "source": [ - "# 8. Adding Kitems into context" + "# 8. KItem Contexts" ] }, { @@ -15,7 +15,7 @@ "source": [ "## 8.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", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your interest, along with installation of this package, and have established access to the DSMS through DSMS-SDK (refer to [Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms))\n", "\n", "Import the needed classes and functions." ] @@ -24,7 +24,14 @@ "cell_type": "code", "execution_count": 1, "id": "8e119a92", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:29.027716Z", + "iopub.status.busy": "2026-06-07T20:53:29.027557Z", + "iopub.status.idle": "2026-06-07T20:53:29.765304Z", + "shell.execute_reply": "2026-06-07T20:53:29.763850Z" + } + }, "outputs": [], "source": [ "from dsms import DSMS, KItem" @@ -42,10 +49,18 @@ "cell_type": "code", "execution_count": 2, "id": "ddc6cdfa", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:29.768054Z", + "iopub.status.busy": "2026-06-07T20:53:29.767679Z", + "iopub.status.idle": "2026-06-07T20:53:30.277028Z", + "shell.execute_reply": "2026-06-07T20:53:30.275922Z" + } + }, "outputs": [], "source": [ - "dsms = DSMS(env=\".env\")" + "import os\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" ] }, { @@ -53,7 +68,7 @@ "id": "7206cca4", "metadata": {}, "source": [ - "## 8.2 Putting KItems into contexts" + "## 8.2. Putting KItems into contexts" ] }, { @@ -69,51 +84,81 @@ "id": "ed1d8179", "metadata": {}, "source": [ - "We will quickly create a KItem of type dataset and we will put it into context with a dataset catalog." + "A KItem that is designated as a context (e.g. a `Project`) can group other KItems. A KItem joins a context by setting its `contexts` field to include the context KItem.\n", + "\n", + "We will create a `Project` as the context and a `Dataset` as its member." ] }, { "cell_type": "code", "execution_count": 3, "id": "b7dcb02f", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:30.279909Z", + "iopub.status.busy": "2026-06-07T20:53:30.279715Z", + "iopub.status.idle": "2026-06-07T20:53:32.498315Z", + "shell.execute_reply": "2026-06-07T20:53:32.496821Z" + } + }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/app/dsms/knowledge/kitem.py:406: UserWarning: A flat dictionary was provided for custom properties.\n", - " Will be transformed into `KItemCustomPropertiesModel`.\n", - " warnings.warn(\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "https://bue.materials-data.space/knowledge/dataset/testdataset-edc21685\n" + "https://nash.materials-data.space/knowledge/project/testproject-e0654a2c\n" ] - }, + } + ], + "source": [ + "# Project is a context-capable KType (its spec has context=True)\n", + "project = KItem(\n", + " name=\"Test project\",\n", + " ktype_id=dsms.ktypes.Project,\n", + ")\n", + "\n", + "dsms.add(project)\n", + "dsms.commit()\n", + "\n", + "print(project.url)" + ] + }, + { + "cell_type": "markdown", + "id": "7fb7d96c", + "metadata": {}, + "source": [ + "> **Note:** Some fields visible in KItem outputs (`authors`, `rdf_exists`, `user_groups`) are deprecated in v5.0.0 and are no longer populated by the server. They remain in the model for backward compatibility. Use `access_properties` for access control." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c7a41db1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:32.500837Z", + "iopub.status.busy": "2026-06-07T20:53:32.500650Z", + "iopub.status.idle": "2026-06-07T20:53:34.900219Z", + "shell.execute_reply": "2026-06-07T20:53:34.899011Z" + } + }, + "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Tensile Strength [MPa]`. Cannot check if value is of correct type.\n", - " warnings.warn(\n", - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Material`. Cannot check if value is of correct type.\n", - " warnings.warn(\n" + "https://nash.materials-data.space/knowledge/dataset/testdataset-340d5940\n" ] } ], "source": [ + "# Assign the dataset to the project context via the contexts field\n", "dataset = KItem(\n", " name=\"Test dataset\",\n", " ktype_id=dsms.ktypes.Dataset,\n", - " custom_properties={\n", - " \"Tensile Strength [MPa]\": 180,\n", - " \"Material\": \"DXD100\"\n", - " }\n", - " )\n", + " contexts=[project],\n", + ")\n", "\n", "dsms.add(dataset)\n", "dsms.commit()\n", @@ -122,48 +167,49 @@ ] }, { - "cell_type": "code", - "execution_count": 4, - "id": "c7a41db1", + "cell_type": "markdown", + "id": "db37aaad", "metadata": {}, + "source": [ + "We can verify the context relationship from both directions: checking that the dataset reports its context, and checking that a freshly-fetched dataset shows `has_contexts=True`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "df636cb5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:34.902434Z", + "iopub.status.busy": "2026-06-07T20:53:34.902259Z", + "iopub.status.idle": "2026-06-07T20:53:34.905926Z", + "shell.execute_reply": "2026-06-07T20:53:34.905197Z" + } + }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.11/site-packages/pydantic/main.py:463: UserWarning: Pydantic serializer warnings:\n", - " PydanticSerializationUnexpectedValue(Expected `str` - serialized value may not be as expected [input_value=False, input_type=bool])\n", - " return self.__pydantic_serializer__.to_python(\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "https://bue.materials-data.space/knowledge/dataset-catalog/testcatalog-4bcfabbb\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/app/dsms/knowledge/kitem.py:692: UserWarning: No webform was defined for entry `Project`. Cannot check if value is of correct type.\n", - " warnings.warn(\n" + "Dataset contexts: [kitem:\n", + " id: e0654a2c-68f7-4398-800a-bf56afd156b5\n", + " name: Test project\n", + " ktype_id: project\n", + " slug: testproject-e0654a2c\n", + " avatar_exists: false\n", + " has_contexts: false\n", + "]\n", + "Dataset has_contexts: False\n" ] } ], "source": [ - "catalog = KItem(\n", - " name=\"Test catalog\",\n", - " ktype_id=dsms.ktypes.DatasetCatalog,\n", - " custom_properties={\"Project\": \"Mechanical testing campaign 1\"},\n", - " contexts=[dataset]\n", - ")\n", + "# From the dataset side: which contexts does this dataset belong to?\n", + "print(\"Dataset contexts:\", dataset.contexts)\n", "\n", - "dsms.add(catalog)\n", - "dsms.commit()\n", - "\n", - "print(catalog.url)" + "# Verify via a fresh fetch\n", + "refreshed_dataset = dsms[str(dataset.id)]\n", + "print(\"Dataset has_contexts:\", refreshed_dataset.has_contexts)" ] }, { @@ -171,64 +217,187 @@ "id": "20026edb", "metadata": {}, "source": [ - "Now we can inspect the catalog and see which datasets are in context:" + "Now we can inspect the project:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "640813be", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:34.907951Z", + "iopub.status.busy": "2026-06-07T20:53:34.907805Z", + "iopub.status.idle": "2026-06-07T20:53:34.913991Z", + "shell.execute_reply": "2026-06-07T20:53:34.913406Z" + } + }, "outputs": [ { "data": { "text/plain": [ "kitem:\n", - " id: 4bcfabbb-dabf-4d4f-a964-2d56db7cdf54\n", - " name: Test catalog\n", - " ktype_id: dataset-catalog\n", - " slug: testcatalog-4bcfabbb\n", + " id: e0654a2c-68f7-4398-800a-bf56afd156b5\n", + " name: Test project\n", + " ktype_id: project\n", + " slug: testproject-e0654a2c\n", + " avatar_exists: false\n", + " has_contexts: false\n", " annotations: []\n", " attachments: []\n", " linked_kitems: []\n", " affiliations: []\n", - " authors:\n", - " - user_id: 7f0e5a37-353b-4bbc-b1f1-b6ad575f562d\n", - " avatar_exists: false\n", + " authors: []\n", " contacts: []\n", - " created_at: 2025-08-13 13:03:06.234294\n", - " updated_at: 2025-08-13 13:03:06.234294\n", + " created_at: 2026-06-07 20:53:30.905592\n", + " updated_at: 2026-06-07 20:53:30.905592\n", " external_links: []\n", " apps: []\n", - " user_groups: []\n", - " custom_properties:\n", - " content:\n", - " sections:\n", - " - id: id1755090185982jzxbpc\n", - " name: Misc\n", - " entries:\n", - " - id: id17550901859828l3uet\n", - " type: Text\n", - " label: Project\n", - " value: Mechanical testing campaign 1\n", - " measurementUnit: null\n", - " relationMapping: null\n", - " required: false\n", " rdf_exists: false\n", - " contexts:\n", - " - id: edc21685-2d25-4d7a-bd2e-7c757371d3f8\n", - " name: Test dataset\n", - " ktype_id: dataset\n", - " slug: testdataset-edc21685" + " access_properties:\n", + " visibility: private\n", + " user_access:\n", + " - role: OWNER\n", + " user_id: 6be66d9f-1a9f-44fc-8176-f71155de06ba\n", + " group_access: []\n", + " contexts: []" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "catalog" + "project" + ] + }, + { + "cell_type": "markdown", + "id": "d59e0f61", + "metadata": {}, + "source": [ + "## 8.3. Searching within a context\n", + "\n", + "`DSMS.search()` accepts a `contexts` parameter: a list of KItem IDs. The search returns only KItems that belong to at least one of the specified contexts." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e6b97a13", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:34.916076Z", + "iopub.status.busy": "2026-06-07T20:53:34.915910Z", + "iopub.status.idle": "2026-06-07T20:53:35.594571Z", + "shell.execute_reply": "2026-06-07T20:53:35.593660Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test dataset | has_contexts: False\n" + ] + } + ], + "source": [ + "# Search for KItems inside the project context\n", + "results = dsms.search(contexts=[str(project.id)])\n", + "for r in results:\n", + " print(r.kitem.name, \"| has_contexts:\", r.kitem.has_contexts)" + ] + }, + { + "cell_type": "markdown", + "id": "1c020217", + "metadata": {}, + "source": [ + "The `has_contexts` field on `KItemCompactedModel` is a boolean that indicates whether the KItem belongs to at least one context. It is populated by the server and available on search results without needing to fetch the full KItem." + ] + }, + { + "cell_type": "markdown", + "id": "32f71fd1", + "metadata": {}, + "source": [ + "## 8.4. Context-scoped SPARQL queries\n", + "\n", + "Two SPARQL methods on `SparqlInterface` operate within the scope of a context KItem rather than the full triplestore.\n", + "\n", + "`sparql_interface.query_context(context_id, query)` sends a standard SPARQL SELECT query scoped to the given context and returns a JSON result object (same format as `sparql_interface.query()`).\n", + "\n", + "`sparql_interface.graph_context(context_id, query)` sends a graph query scoped to the context and returns a JSON graph result." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "875b69d5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:35.596751Z", + "iopub.status.busy": "2026-06-07T20:53:35.596591Z", + "iopub.status.idle": "2026-06-07T20:53:35.678070Z", + "shell.execute_reply": "2026-06-07T20:53:35.677223Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: context SPARQL requires RDF knowledge graphs for context members.\n", + "Error: Context SPARQL query was not successful: {\"detail\":\"No KG.kitem.ttl attachments found for context members. Ensure knowledge graphs have been generated for the members.\"}\n" + ] + } + ], + "source": [ + "sparql_query = \"\"\"\n", + "SELECT ?s ?p ?o\n", + "WHERE {\n", + " ?s ?p ?o .\n", + "}\n", + "LIMIT 10\n", + "\"\"\"\n", + "\n", + "try:\n", + " results = dsms.sparql_interface.query_context(str(project.id), sparql_query)\n", + " print(results)\n", + "except RuntimeError as e:\n", + " print(f\"Note: context SPARQL requires RDF knowledge graphs for context members.\")\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "6d37b2bf", + "metadata": {}, + "source": [ + "## 8.5. Cleanup\n", + "\n", + "Delete the KItems created during this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6f764bfd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-07T20:53:35.680027Z", + "iopub.status.busy": "2026-06-07T20:53:35.679870Z", + "iopub.status.idle": "2026-06-07T20:53:36.536942Z", + "shell.execute_reply": "2026-06-07T20:53:36.535707Z" + } + }, + "outputs": [], + "source": [ + "del dsms[dataset]\n", + "del dsms[project]\n", + "dsms.commit()" ] } ], @@ -248,7 +417,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/docs/dsms_sdk/tutorials/9_semantic_schemas.ipynb b/docs/dsms_sdk/tutorials/9_semantic_schemas.ipynb new file mode 100644 index 0000000..27d44ee --- /dev/null +++ b/docs/dsms_sdk/tutorials/9_semantic_schemas.ipynb @@ -0,0 +1,311 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 9. Semantic Schemas\n", + "\n", + "This tutorial shows how to attach semantic schema data to a KItem using the SDK.\n", + "Semantic schemas are defined in the k-type spec and map a KItem's scientific content\n", + "to ontology-typed RDF nodes. They are distinct from `custom_properties`, which drive\n", + "the UI form.\n", + "\n", + "By the end of this tutorial you will know:\n", + "\n", + "- what `custom_properties` and `schema_data` each represent,\n", + "- how to populate both at construction time using the same input style,\n", + "- how to use the `populate_schema()` method for post-construction updates." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.1. Setting up\n", + "\n", + "Before you run this tutorial: make sure to have access to a DSMS-instance of your\n", + "interest, along with installation of this package, and have established access to the\n", + "DSMS through DSMS-SDK (refer to\n", + "[Connecting to DSMS](../dsms_sdk.md#connecting-to-dsms)).\n", + "\n", + "Import the needed classes and functions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from dsms import DSMS, KItem\n", + "\n", + "dsms = DSMS(env=\".env\") if os.path.exists(\".env\") else DSMS()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.2. Custom properties vs. semantic schemas\n", + "\n", + "A KItem can carry two complementary representations of its data:\n", + "\n", + "| | `custom_properties` | `schema_data` |\n", + "|---|---|---|\n", + "| **Purpose** | UI form displayed on the platform | OO-LD / RDF graph for SPARQL and semantic reasoning |\n", + "| **Defined by** | K-type webform schema | K-type semantic schema spec (v2) |\n", + "| **Input format** | Flat dict keyed by field label | Flat dict keyed by transform field names |\n", + "| **Set at init?** | Yes | Yes |\n", + "\n", + "Fields such as `name` appear in both representations and should be kept consistent.\n", + "Geometry and administrative metadata typically live in `custom_properties`;\n", + "measurement results, provenance, and ontology-typed relationships live in `schema_data`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.3. Discovering available semantic schemas\n", + "\n", + "The k-type spec lists which semantic schemas a KItem of that type can carry.\n", + "Fetch the v2 spec for any k-type to see the available schema IDs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ktype_v2 = dsms.get_v2_ktype(\"tensile-test\")\n", + "for s in ktype_v2.spec.resolved_semantic_schemas:\n", + " print(s.id, \"\u2192\", s.url)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.4. Setting both at construction time\n", + "\n", + "Both `custom_properties` and `schema_data` accept a plain dict at construction.\n", + "For `schema_data`, pass `{schema_id: simplified_input_dict}`. The schema transform\n", + "is fetched and applied immediately; subsequent constructions with the same schema\n", + "URL use a process-level cache, keeping the cost comparable to `custom_properties`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "specimen = KItem(\n", + " name=\"Specimen-TT-01\",\n", + " ktype_id=dsms.ktypes.Specimen,\n", + " custom_properties={\n", + " \"Specimen type\": \"flat\",\n", + " \"Width\": 12.5,\n", + " \"Length\": 80.0,\n", + " \"Thickness\": 1.5,\n", + " },\n", + " schema_data={\n", + " \"specimen/PMDCo\": {\n", + " \"label\": \"Specimen-TT-01\",\n", + " \"width_mm\": 12.5,\n", + " \"length_mm\": 80.0,\n", + " \"thickness_mm\": 1.5,\n", + " }\n", + " },\n", + ")\n", + "\n", + "specimen" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Commit to the platform. The `schema_data` is already resolved to OO-LD\n", + "and will be persisted as-is.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dsms.add(specimen)\n", + "dsms.commit()\n", + "specimen.url" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `schema_data` already holds the resolved OO-LD content right after\n", + "construction \u2014 no need to wait for a commit to inspect it.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for entry in specimen.schema_data:\n", + " print(\"schema_id:\", entry.schema_id)\n", + " print(\"content keys:\", list(entry.content.keys()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.5. Using `populate_schema()` for post-construction updates\n", + "\n", + "`populate_schema(schema_id, input_data)` is the method-call equivalent. Use it\n", + "when you need to add or replace a schema entry after the KItem has been constructed,\n", + "or when you want explicit control over the transform step.\n", + "\n", + "It returns `self`, so calls can be chained." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tensile_test = KItem(\n", + " name=\"TensileTest-01\",\n", + " ktype_id=dsms.ktypes.TensileTest,\n", + " custom_properties={\n", + " \"Identifier\": \"TT-2024-001\",\n", + " \"Start time\": \"2024-03-15T09:00:00\",\n", + " \"End time\": \"2024-03-15T09:45:00\",\n", + " },\n", + ")\n", + "\n", + "tensile_test.populate_schema(\n", + " \"characterization/tensile-test/TTO\",\n", + " {\n", + " \"test_name\": \"TT-2024-001\",\n", + " \"specimen_iri\": str(specimen.id),\n", + " \"results\": [\n", + " {\"property\": \"YieldStrength\", \"value\": 350.0, \"unit\": \"MPa\"},\n", + " {\"property\": \"UltimateTensileStrength\", \"value\": 490.0, \"unit\": \"MPa\"},\n", + " {\"property\": \"Elongation\", \"value\": 28.5, \"unit\": \"%\"},\n", + " ],\n", + " },\n", + ")\n", + "\n", + "dsms.add(tensile_test)\n", + "dsms.commit()\n", + "tensile_test.url" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.6. Inspecting the RDF subgraph\n", + "\n", + "The platform generates an RDF subgraph from `schema_data` asynchronously after\n", + "each commit. Use `kitem.subgraph` to retrieve and inspect it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "time.sleep(3) # allow server-side graph generation to complete\n", + "\n", + "try:\n", + " print(tensile_test.subgraph.serialize(format=\"turtle\"))\n", + "except ValueError:\n", + " print(\"Subgraph not yet available \u2014 retry in a moment.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.7. Updating a schema entry\n", + "\n", + "Calling `populate_schema()` with the same `schema_id` replaces the existing entry.\n", + "The dict shorthand at construction works the same way." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tensile_test.populate_schema(\n", + " \"characterization/tensile-test/TTO\",\n", + " {\n", + " \"test_name\": \"TT-2024-001\",\n", + " \"specimen_iri\": str(specimen.id),\n", + " \"results\": [\n", + " {\"property\": \"YieldStrength\", \"value\": 355.0, \"unit\": \"MPa\"},\n", + " {\"property\": \"UltimateTensileStrength\", \"value\": 495.0, \"unit\": \"MPa\"},\n", + " {\"property\": \"Elongation\", \"value\": 29.0, \"unit\": \"%\"},\n", + " ],\n", + " },\n", + ")\n", + "\n", + "dsms.add(tensile_test)\n", + "dsms.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9.8. Cleanup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "del dsms[tensile_test]\n", + "del dsms[specimen]\n", + "dsms.commit()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbformat_minor": 5, + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/index.md b/docs/index.md index 5cd203a..bbacaab 100644 --- a/docs/index.md +++ b/docs/index.md @@ -60,6 +60,7 @@ The Data Space Management System dsms_sdk/dsms_sdk dsms_sdk/dsms_config_schema dsms_sdk/dsms_kitem_schema +release-checklist ``` diff --git a/docs/release-checklist.md b/docs/release-checklist.md new file mode 100644 index 0000000..94d4a2c --- /dev/null +++ b/docs/release-checklist.md @@ -0,0 +1,215 @@ +# Release Checklist + +Work through this list in order when preparing a new SDK release. Each section states which files to touch and what to verify. + +Commands assume your working directory is the **repository root** and your virtual environment is active: + +```bash +python -m venv .venv && source .venv/bin/activate +pip install -e ".[docs,tests,pre_commit]" +``` + +--- + +## 0. Classify the change + +Determine the semantic-versioning impact before touching any file: + +| Change type | Version bump | Examples | +|---|---|---| +| Typo, doc fix, internal refactor with no API change | **patch** (x.y.Z) | Fix docstring, rename internal variable | +| New field, method, or parameter (backward-compatible) | **minor** (x.Y.0) | Add `schema_data` field to `KItem` | +| Removed or renamed public API, breaking model change | **major** (X.0.0) | Rename `user_groups` to `access_properties`, drop Python version | + +--- + +## 1. `setup.cfg` + +- [ ] Bump `version` to the new version string (format: `vMAJOR.MINOR.PATCH`). +- [ ] Update `python_requires` if the minimum Python version changed. +- [ ] Add, remove, or relax dependency pins in `install_requires` if needed. + +**Verify:** + +```bash +python -c "import configparser; c = configparser.ConfigParser(); c.read('setup.cfg'); print(c['metadata']['version'])" +``` + +--- + +## 2. `CHANGELOG.md` + +- [ ] Add a new version section **above** the previous entry: + + ```markdown + ## [x.y.z] — YYYY-MM-DD + + ### Added + - ... + + ### Changed + - ... + + ### Deprecated + - ... + + ### Removed + - ... + + ### Fixed + - ... + ``` + +- [ ] Use today's date in `YYYY-MM-DD` format. +- [ ] Cover every public API change, new model, and deprecation. + +--- + +## 3. `README.md` + +- [ ] Update the compatibility table: add a row for the new SDK version and its required DSMS backend version. +- [ ] Update the Usage capabilities list if new top-level features were added. + +--- + +## 4. `docs/dsms_sdk/dsms_sdk.md` + +- [ ] Update the compatibility table to match `README.md`. +- [ ] Update the numbered capabilities list if the feature set changed. + +--- + +## 5. `docs/dsms_sdk/dsms_kitem_schema.md` + +Update this file when any of the following change: + +- [ ] New or removed fields on `KItem` or `KItemCompactedModel`: update the field tables. +- [ ] New property sub-models (e.g. `KItemAccessProperties`, `KItemSchemaData`): add a dedicated section with field table and example. +- [ ] New or removed `Widget` enum values: update the Widget Fields table. +- [ ] Deprecated fields: mark them clearly in the table and add a deprecation note. + +--- + +## 6. `docs/dsms_sdk/dsms_config_schema.md` + +- [ ] Update the Configuration Fields table if any `Configuration` fields were added, removed, or changed. + +--- + +## 7. Tutorial notebooks + +Update notebooks when any of the following change: + +- [ ] Public API signatures (`DSMS.search()`, `DSMS.get_kitems()`, `KItem` fields, etc.) +- [ ] New features worth demonstrating (new search filters, new KType v2 methods, new property models) +- [ ] Deprecated fields used in notebook code cells + +Work cell by cell. Do **not** hand-edit output cells; regenerate them with the refresh script (see step 8). + +Notebooks live in `docs/dsms_sdk/tutorials/`. Specific guidance: + +| Notebook | When to update | +|---|---| +| `1_introduction.ipynb` | KItem/KType field changes, new top-level SDK features | +| `2_creation.ipynb` | New KItem fields, new `access_properties` / `schema_data` patterns | +| `3_updating.ipynb` | Changed update workflows, new updatable fields | +| `4_deletion.ipynb` | Changed deletion behaviour | +| `5_search.ipynb` | New `DSMS.search()` parameters or filters | +| `6_apps.ipynb` | App config or pipeline changes | +| `7_ktypes.ipynb` | KType v1 or v2 API changes | +| `8_kitem_contexts.ipynb` | Context model or context-scoped SPARQL changes | + +--- + +## 8. Run and refresh notebooks + +> **Requirement:** a live DSMS instance must be reachable. Set credentials in a `.env` file or via environment variables (`DSMS_HOST_URL`, `DSMS_USERNAME`, `DSMS_PASSWORD` or `DSMS_TOKEN`) before running. + +**Test all notebooks without saving outputs (CI-safe):** + +```bash +./scripts/run_notebooks.sh +``` + +**Re-execute and save outputs in-place (for documentation commits):** + +```bash +./scripts/run_notebooks.sh --refresh +``` + +**Refresh a single notebook:** + +```bash +./scripts/run_notebooks.sh --refresh docs/dsms_sdk/tutorials/7_ktypes.ipynb +``` + +Inspect the saved outputs before committing: + +- Every code cell must have output (no silent failures). +- No cell output should contain a Python traceback. +- Deprecated-field warnings (if any) should appear in the expected cells only. + +--- + +## 9. Pre-commit hooks + +Run all linters and formatters across every changed Python file: + +```bash +pre-commit run --files ... +``` + +All hooks must pass before committing. Do not use `--no-verify`. + +--- + +## 10. Commit and tag + +- [ ] Stage files by name; do not use `git add .` or `git add -A`. +- [ ] Write a concise commit message: `release: bump to vX.Y.Z` +- [ ] Tag the release commit: + + ```bash + git tag vX.Y.Z + git push origin vX.Y.Z + ``` + +- [ ] Verify the tag is visible on the remote: + + ```bash + git ls-remote --tags origin | grep vX.Y.Z + ``` + +--- + +## 11. PyPI publish + +- [ ] Build the distribution: + + ```bash + python -m build + ``` + +- [ ] Upload to PyPI: + + ```bash + twine upload dist/dsms_sdk-X.Y.Z* + ``` + +- [ ] Verify the new version is visible at https://pypi.org/project/dsms-sdk/. + +--- + +## Quick reference + +| File | patch | minor | major | +|---|:---:|:---:|:---:| +| `setup.cfg` (version) | yes | yes | yes | +| `CHANGELOG.md` | yes | yes | yes | +| `README.md` (compat table) | no | yes | yes | +| `README.md` (capabilities) | no | maybe | yes | +| `docs/dsms_sdk/dsms_sdk.md` | no | maybe | yes | +| `docs/dsms_sdk/dsms_kitem_schema.md` | no | yes | yes | +| `docs/dsms_sdk/dsms_config_schema.md` | no | maybe | yes | +| Tutorial notebooks | no | maybe | yes | +| Run `scripts/run_notebooks.sh` | no | yes | yes | diff --git a/dsms/apps/config.py b/dsms/apps/config.py index 42276b6..0da9a51 100644 --- a/dsms/apps/config.py +++ b/dsms/apps/config.py @@ -1,4 +1,5 @@ """DSMS app models""" + import logging import urllib.parse import warnings @@ -20,7 +21,6 @@ from dsms.core.session import Session # isort:skip - logger = logging.getLogger(__name__) logger.addHandler(handler) logger.propagate = False diff --git a/dsms/core/__init__.py b/dsms/core/__init__.py index 68a7e0c..d95d2c0 100644 --- a/dsms/core/__init__.py +++ b/dsms/core/__init__.py @@ -1,5 +1,5 @@ -"""DSMS core module -""" +"""DSMS core module""" + from dsms.core.configuration import Configuration __all__ = ["Configuration"] diff --git a/dsms/core/configuration.py b/dsms/core/configuration.py index 3b842f6..229fcc4 100644 --- a/dsms/core/configuration.py +++ b/dsms/core/configuration.py @@ -43,7 +43,13 @@ class Loglevel(Enum): WARNING = logging.WARNING -class Configuration(BaseSettings): +class BaseConfiguration(BaseSettings): + """Base Configuration for DSMS-SDK""" + + model_config = ConfigDict(use_enum_values=True) + + +class Configuration(BaseConfiguration): """General config for DSMS-SDK""" host_url: AnyUrl = Field( diff --git a/dsms/core/dsms.py b/dsms/core/dsms.py index bbc6a67..d9b2106 100644 --- a/dsms/core/dsms.py +++ b/dsms/core/dsms.py @@ -3,7 +3,7 @@ import os import warnings from enum import Enum -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union from uuid import UUID from dotenv import load_dotenv @@ -14,23 +14,61 @@ from dsms.core.session import Session from dsms.core.utils import _ping_backend from dsms.knowledge.kitem import KItem -from dsms.knowledge.ktype import KType, ProcessSchema +from dsms.knowledge.ktype import ( + CreateKTypeRequest, + KType, + KTypeSpec, + KTypeSpecPayload, + KTypeV2, + ProcessSchema, + RemoteDiffOut, + RemoteKTypeSummary, + RemoteKTypeVersion, + RemoteSchemaInfo, +) from dsms.knowledge.sparql_interface import SparqlInterface from dsms.knowledge.utils import _search from dsms.knowledge.utils import ( # isort:skip + _add_group_member, + _add_group_to_group, _commit, + _create_group, + _delete_group, + _get_group_members, + _get_group_subgroups, _get_kitem, _get_kitem_list, + _get_ktypes_by_parent, _get_remote_ktypes, _get_process_schemas, _get_webform_schemas, + _get_schema_data, + _get_user_groups, + _get_user_list, + _remove_group_from_group, + _remove_group_member, + _update_group, + _v2_create_ktype, + _v2_delete_ktype, + _v2_export_ktype, + _v2_get_ktype, + _v2_import_ktype, + _v2_list_ktypes, + _v2_list_remote_ktypes, + _v2_list_remote_schemas, + _v2_list_remote_versions, + _v2_refresh_ktype, + _v2_remote_diff, + _v2_restore_stash, + _v2_update_ktype, + get_user_by_id, ) if TYPE_CHECKING: - from typing import Optional - from dsms.core.session import Buffers + from dsms.knowledge.groups import Group, GroupList, User + from dsms.knowledge.properties.schema_data import KItemSchemaData from dsms.knowledge.search import KItemListModel, SearchResult @@ -80,6 +118,8 @@ def __init__( self._config = None self._ktypes = None + self._user_groups = None + self._users = None self._session.dsms = self if env: @@ -94,12 +134,10 @@ def __init__( elif config is None: self.config = Configuration(**kwargs) else: - raise ValueError( - """`config`-keyword is defined among others. + raise ValueError("""`config`-keyword is defined among others. The `config`-keyword is reserved for passing a config-object directly. Please specify kwargs for to be passed to the `Configuration`-object _OR_ - an instance of this `Configuration`-object directly.""" - ) + an instance of this `Configuration`-object directly.""") self._sparql_interface = SparqlInterface(self) if self.config.auto_fetch_ktypes: @@ -204,6 +242,9 @@ def search( offset: int = 0, allow_fuzzy: "Optional[bool]" = True, compact: "Optional[bool]" = False, + contexts: "Optional[List[str]]" = None, + attachment_extensions: "Optional[List[str]]" = None, + visibility: "Optional[Literal['private', 'internal', 'public']]" = None, ) -> "List[SearchResult]": """Search for KItems in the remote backend.""" return _search( @@ -215,6 +256,9 @@ def search( offset, allow_fuzzy, compact, + contexts, + attachment_extensions, + visibility, ) @property @@ -302,16 +346,26 @@ def kitems(self) -> "KItemListModel": warnings.warn(message, DeprecationWarning) return _get_kitem_list(self) - def get_kitems(self, limit=10, offset=0) -> "KItemListModel": + def get_kitems( + self, + user_id: Optional[str] = None, + limit=10, + offset=0, + name: Optional[str] = None, + ) -> "KItemListModel": """ Get all available KItems from the remote backend. Args: + user_id (str, optional): Filter by user ID. limit (int): The amount of KItems to be returned. Defaults to 10. offset (int): The offset in the list of KItems. Defaults to 0. + name (str, optional): Filter by KItem name. """ - return _get_kitem_list(self, limit=limit, offset=offset) + return _get_kitem_list( + self, user_id=user_id, limit=limit, offset=offset, name=name + ) @property def app_configs(self) -> "List[AppConfig]": @@ -331,6 +385,262 @@ def session(self) -> "Session": """Return DSMS session""" return self._session + @property + def user_groups(self) -> "List[Group]": + """Return user groups, fetching from the backend on first access. + + Results are cached for the lifetime of this DSMS instance. + Call refresh_user_groups() to force a re-fetch. + """ + if self._user_groups is None: + self._user_groups = _get_user_groups(self) + return self._user_groups + + def refresh_user_groups(self) -> None: + """Re-fetch user groups from the backend and update the local cache.""" + self._user_groups = _get_user_groups(self) + + @property + def users(self) -> "List[User]": + """Return all users, fetching from the backend on first access. + + Results are cached for the lifetime of this DSMS instance. + Call refresh_users() to force a re-fetch. + """ + if self._users is None: + self._users = _get_user_list(self) + return self._users + + def refresh_users(self) -> None: + """Re-fetch users from the backend and update the local cache.""" + self._users = _get_user_list(self) + + def get_user(self, user_id: str) -> "User": + """Fetch a single user by ID from the backend. + + Args: + user_id: The unique identifier of the user. + + Returns: + User object for the given ID. + """ + return get_user_by_id(self, user_id) + + def get_group_members(self, group_id: str) -> "List[User]": + """Fetch the members of a group (including subgroup members). + + Args: + group_id: The unique identifier of the group. + + Returns: + List of User objects belonging to the group. + """ + return _get_group_members(self, group_id) + + def create_group( + self, + name: str, + description: str = "", + parent_id: Optional[str] = None, + ) -> "Group": + """Create a new group. + + Args: + name: Name of the group. + description: Optional description. + parent_id: ID of the parent group. If None, creates a top-level group. + + Returns: + The created Group object. + """ + from dsms.knowledge.groups import Group # noqa: F401 + + group = _create_group(self, name, description, parent_id) + self._user_groups = None # invalidate cache + return group + + def update_group( + self, + group_id: str, + name: Optional[str] = None, + description: Optional[str] = None, + ) -> "Group": + """Update the name or description of an existing group. + + Args: + group_id: The unique identifier of the group. + name: New name. Pass None to leave unchanged. + description: New description. Pass None to leave unchanged. + + Returns: + The updated Group object. + """ + from dsms.knowledge.groups import Group # noqa: F401 + + group = _update_group(self, group_id, name, description) + self._user_groups = None # invalidate cache + return group + + def delete_group(self, group_id: str) -> None: + """Delete a group. + + Args: + group_id: The unique identifier of the group to delete. + """ + _delete_group(self, group_id) + self._user_groups = None # invalidate cache + + def add_group_member(self, group_id: str, user_id: str) -> None: + """Add a user to a group. + + Args: + group_id: The unique identifier of the group. + user_id: The unique identifier of the user to add. + """ + _add_group_member(self, group_id, user_id) + + def remove_group_member(self, group_id: str, user_id: str) -> None: + """Remove a user from a group. + Args: + group_id: The unique identifier of the group. + user_id: The unique identifier of the user to remove. + """ + _remove_group_member(self, group_id, user_id) + + def get_group_subgroups(self, group_id: str) -> "GroupList": + """Return the direct child groups of a group. + + Args: + group_id: The unique identifier of the parent group. + + Returns: + GroupList of direct child groups. + """ + return _get_group_subgroups(self, group_id) + + def add_group_to_group(self, parent_id: str, child_id: str) -> None: + """Link an existing group as a direct child of another group. + + The child group is moved in the hierarchy; its members and any of its + own subgroups are preserved. Members of the child group are resolved + recursively when listing the parent group's members. + + Args: + parent_id: The unique identifier of the parent group. + child_id: The unique identifier of the group to nest as a child. + """ + _add_group_to_group(self, parent_id, child_id) + self._user_groups = None + + def remove_group_from_group(self, parent_id: str, child_id: str) -> None: + """Detach a child group from its parent and promote it back to top-level. + + Args: + parent_id: The unique identifier of the parent group. + child_id: The unique identifier of the child group to detach. + """ + _remove_group_from_group(self, parent_id, child_id) + self._user_groups = None + + def get_schema_data(self, kitem_id: str) -> "List[KItemSchemaData]": + """Fetch all schema-data entries for a KItem from the remote backend. + + Args: + kitem_id: The unique identifier of the KItem. + + Returns: + List of KItemSchemaData entries. + """ + from dsms.knowledge.properties.schema_data import KItemSchemaData + + return [ + KItemSchemaData(**entry) + for entry in _get_schema_data(self, kitem_id) + ] + + # ------------------------------------------------------------------ + # KType helpers + # ------------------------------------------------------------------ + + def get_ktypes_by_parent(self, parent_id: str) -> List[KType]: + """Return KTypes whose extends chain contains parent_id.""" + return [KType(**kt) for kt in _get_ktypes_by_parent(self, parent_id)] + + # ------------------------------------------------------------------ + # KType v2 API + # ------------------------------------------------------------------ + + def get_v2_ktypes(self) -> List[KTypeV2]: + """List all v2 KTypes (spec=None for v1-only types).""" + return [KTypeV2(**kt) for kt in _v2_list_ktypes(self)] + + def get_v2_ktype(self, ktype_id: str) -> KTypeV2: + """Fetch a single v2 KType by ID.""" + return KTypeV2(**_v2_get_ktype(self, ktype_id)) + + def create_v2_ktype(self, request: CreateKTypeRequest) -> KTypeV2: + """Create or upgrade a KType to v2.""" + return KTypeV2( + **_v2_create_ktype(self, request.model_dump(exclude_none=True)) + ) + + def import_v2_ktype(self, url: str) -> KTypeV2: + """Import a KType spec from a GitHub URL.""" + return KTypeV2(**_v2_import_ktype(self, url)) + + def update_v2_ktype( + self, ktype_id: str, payload: KTypeSpecPayload + ) -> KTypeV2: + """Partially update a v2 KType spec.""" + return KTypeV2( + **_v2_update_ktype( + self, ktype_id, payload.model_dump(exclude_none=True) + ) + ) + + def delete_v2_ktype(self, ktype_id: str) -> None: + """Delete a v2 KType (blocked if KItems exist).""" + _v2_delete_ktype(self, ktype_id) + + def restore_v2_ktype_stash(self, ktype_id: str) -> KTypeV2: + """Restore the pre-import stash for a v2 KType.""" + return KTypeV2(**_v2_restore_stash(self, ktype_id)) + + def refresh_v2_ktype(self, ktype_id: str) -> KTypeV2: + """Re-fetch a v2 KType spec from its stored source URL.""" + return KTypeV2(**_v2_refresh_ktype(self, ktype_id)) + + def export_v2_ktype(self, ktype_id: str) -> str: + """Download a v2 KType spec as YAML text.""" + return _v2_export_ktype(self, ktype_id) + + def get_v2_ktype_spec(self, ktype_id: str) -> Optional[KTypeSpec]: + """Return the KTypeSpec for a v2 KType, or None if not a v2 type.""" + return self.get_v2_ktype(ktype_id).spec + + def list_remote_v2_ktypes(self) -> List[RemoteKTypeSummary]: + """List KTypes available in the remote GitHub repository.""" + return [ + RemoteKTypeSummary(**kt) for kt in _v2_list_remote_ktypes(self) + ] + + def list_remote_schemas(self) -> List[RemoteSchemaInfo]: + """List semantic schemas available in the remote GitHub repository.""" + return [RemoteSchemaInfo(**s) for s in _v2_list_remote_schemas(self)] + + def list_remote_ktype_versions( + self, ktype_id: str + ) -> List[RemoteKTypeVersion]: + """List all GitHub-tagged versions of a v2 KType.""" + return [ + RemoteKTypeVersion(**v) + for v in _v2_list_remote_versions(self, ktype_id) + ] + + def get_v2_ktype_remote_diff(self, ktype_id: str) -> RemoteDiffOut: + """Compare the local v2 KType spec against the latest remote version.""" + return RemoteDiffOut(**_v2_remote_diff(self, ktype_id)) + @classmethod def __get_pydantic_core_schema__(cls): """Get validator of the DSMS-object.""" @@ -340,10 +650,8 @@ def __get_pydantic_core_schema__(cls): def verify_connection(dsms: DSMS) -> None: """Check if DSMS is valid.""" if not isinstance(dsms, DSMS): - raise TypeError( - f"""The passed object for the dsms-connection - is not of type {DSMS}.""" - ) + raise TypeError(f"""The passed object for the dsms-connection + is not of type {DSMS}.""") if dsms.config.ping_backend: try: response = _ping_backend(dsms) diff --git a/dsms/core/utils.py b/dsms/core/utils.py index 38cd3e4..ef7f073 100644 --- a/dsms/core/utils.py +++ b/dsms/core/utils.py @@ -1,4 +1,5 @@ """Core utils of the DSMS core""" + import json import logging import re diff --git a/dsms/knowledge/__init__.py b/dsms/knowledge/__init__.py index 6226e74..2465f97 100644 --- a/dsms/knowledge/__init__.py +++ b/dsms/knowledge/__init__.py @@ -1,12 +1,50 @@ """Knowledge Module of the DSMS""" from dsms.knowledge.kitem import KItem, KItemCompactedModel -from dsms.knowledge.ktype import KType, ProcessSchema, WebformSchema +from dsms.knowledge.ktype import ( + CreateKTypeRequest, + ImportFromUrlRequest, + KType, + KTypeSpec, + KTypeSpecPayload, + KTypeV2, + OntologyClassSpec, + ProcessSchema, + RelationSpec, + RemoteDiffOut, + RemoteKTypeSummary, + RemoteKTypeVersion, + RemoteSchemaInfo, + RemoteSchemaVersionInfo, + SemanticSchemaRef, + SpecDiffField, + WebformSchema, +) +from dsms.knowledge.properties.schema_data import ( + KItemSchemaData, + KItemSchemaDataList, +) __all__ = [ "KItem", - "KType", "KItemCompactedModel", + "KItemSchemaData", + "KItemSchemaDataList", + "KType", + "KTypeSpec", + "KTypeSpecPayload", + "KTypeV2", + "CreateKTypeRequest", + "ImportFromUrlRequest", + "OntologyClassSpec", "ProcessSchema", + "RelationSpec", + "RemoteDiffOut", + "RemoteKTypeSummary", + "RemoteKTypeVersion", + "RemoteSchemaInfo", + "RemoteSchemaVersionInfo", + "SemanticSchemaRef", + "SpecDiffField", "WebformSchema", ] diff --git a/dsms/knowledge/cli.py b/dsms/knowledge/cli.py index d52d5fa..d17d815 100644 --- a/dsms/knowledge/cli.py +++ b/dsms/knowledge/cli.py @@ -1,6 +1,5 @@ """Command line interface for kitem inspection""" - import click from dsms import DSMS diff --git a/dsms/knowledge/compacted.py b/dsms/knowledge/compacted.py index d615002..af51217 100644 --- a/dsms/knowledge/compacted.py +++ b/dsms/knowledge/compacted.py @@ -1,7 +1,7 @@ """Compacted Knowledge Item implementation of the DSMS""" from enum import Enum -from typing import Optional, Union +from typing import List, Optional, Union from uuid import UUID, uuid4 from pydantic import ( # isort: skip @@ -44,6 +44,16 @@ class KItemCompactedModel(KItemBaseModel): min_length=4, max_length=1000, ) + avatar_exists: Optional[bool] = Field( + False, description="Whether the KItem holds an avatar or not." + ) + has_contexts: bool = Field( + False, description="Whether the KItem belongs to any context." + ) + attachment_extensions: Optional[List[str]] = Field( + None, + description="Unique file extensions present in the KItem's attachments.", + ) def __str__(self) -> str: """Pretty print the kitem fields""" diff --git a/dsms/knowledge/groups/__init__.py b/dsms/knowledge/groups/__init__.py new file mode 100644 index 0000000..7f2af40 --- /dev/null +++ b/dsms/knowledge/groups/__init__.py @@ -0,0 +1,11 @@ +"""DSMS User Groups Module.""" + +from .models import BaseGroup, Group, GroupList, User, UserList + +__all__ = [ + "Group", + "GroupList", + "User", + "BaseGroup", + "UserList", +] diff --git a/dsms/knowledge/groups/models.py b/dsms/knowledge/groups/models.py new file mode 100644 index 0000000..d3b339d --- /dev/null +++ b/dsms/knowledge/groups/models.py @@ -0,0 +1,140 @@ +"""DSMS User Groups Module.""" + +from typing import List, Optional + +import yaml +from pydantic import BaseModel, Field + +from dsms.core.session import Session + + +class User(BaseModel): + """User Model""" + + id: str = Field(..., description="The unique identifier of the user.") + username: str = Field(..., description="The username of the user.") + firstName: Optional[str] = Field( + None, description="First name of the user." + ) + lastName: Optional[str] = Field(None, description="Last name of the user.") + email: Optional[str] = Field( + None, description="Email address of the user." + ) + user_groups: Optional[List["BaseGroup"]] = Field( + None, description="A list of groups the user belongs to." + ) + + def __repr__(self) -> str: + return str(self) + + def __str__(self): + from dsms.knowledge.utils import print_model + + return print_model( + self, + "user", + exclude_extra=Session.dsms.config.hide_properties, + ) + + +class UserList(list): + """List of Users with utility methods.""" + + def __repr__(self) -> str: + return str(self) + + def __str__(self): + from dsms.knowledge.utils import dump_model + + return yaml.dump( + [ + dump_model( + connection, + exclude_extra=Session.dsms.config.hide_properties, + ) + for connection in self + ] + ) + + @property + def by_id(self) -> dict[str, User]: + """Return a dictionary of users indexed by their ID.""" + return {user.id: user for user in self} + + @property + def by_username(self) -> dict[str, User]: + """Return a dictionary of users indexed by their username.""" + return {user.username: user for user in self} + + @property + def by_name(self) -> dict[str, User]: + """Alias for by_username.""" + return self.by_username + + def __getitem__(self, user_id: str) -> User: + """Get a user by ID.""" + return self.by_id[user_id] + + +class BaseGroup(BaseModel): + """Flat group model — id and name only, no subgroups.""" + + id: str = Field(..., description="The unique identifier of the group.") + name: str = Field(..., description="The name of the group.") + + +class Group(BaseGroup): + """Group model with optional subgroup hierarchy.""" + + subgroups: Optional[List["Group"]] = Field( + None, description="A list of subgroups." + ) + + +class GroupList(list): + """List of Groups (may be hierarchical) with utility methods.""" + + def __repr__(self) -> str: + return str(self) + + def __str__(self): + from dsms.knowledge.utils import dump_model + + return yaml.dump( + [ + dump_model( + g, + exclude_extra=Session.dsms.config.hide_properties, + ) + for g in self + ] + ) + + @property + def flat(self) -> List[BaseGroup]: + """Return a flat list of BaseGroup objects for all groups and subgroups.""" + result: List[BaseGroup] = [] + + def _flatten(groups: List[Group]): + for group in groups: + result.append( + BaseGroup(**group.model_dump(exclude={"subgroups"})) + ) + if group.subgroups: + _flatten(group.subgroups) + + _flatten(self) + return result + + @property + def by_id(self) -> dict[str, BaseGroup]: + """Return a dictionary of groups indexed by their ID.""" + return {group.id: group for group in self.flat} + + @property + def by_name(self) -> dict[str, BaseGroup]: + """Return a dictionary of groups indexed by their name.""" + return {group.name: group for group in self.flat} + + +Group.model_rebuild() diff --git a/dsms/knowledge/kitem.py b/dsms/knowledge/kitem.py index 66589a3..ab09352 100644 --- a/dsms/knowledge/kitem.py +++ b/dsms/knowledge/kitem.py @@ -16,6 +16,7 @@ ValidationInfo, field_validator, field_serializer, + model_validator, ) from dsms.core.logging import handler # isort:skip @@ -42,12 +43,12 @@ DataFrameContainer, Column, KItemRelationshipModel, + KItemSchemaData, LinkedKItemsList, Summary, - UserGroup, + KItemAccessProperties, ) - from dsms.knowledge.ktype import KType # isort:skip from dsms.knowledge.utils import ( # isort:skip @@ -117,8 +118,8 @@ class KItem(KItemCompactedModel): 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. + access_properties (Optional[KItemAccessProperties]): + Access control configuration for the KItem. custom_properties (Optional[Any]): Custom properties associated with the KItem. dataframe (Optional[Union[List[Column], pd.DataFrame, Dict[str, Union[List, Dict]]]]): @@ -145,10 +146,9 @@ class KItem(KItemCompactedModel): description="Affiliations related to a KItem.", ) authors: List[Union[Author, str]] = Field( - [], description="Authorship of the KItem." - ) - avatar_exists: Optional[bool] = Field( - False, description="Whether the KItem holds an avatar or not." + [], + description="Authorship of the KItem. Deprecated: no longer populated by the backend.", + deprecated=True, ) contacts: List[ContactInfo] = Field( [], @@ -172,10 +172,6 @@ class KItem(KItemCompactedModel): summary: Optional[Union[str, Summary]] = Field( None, description="Human readable summary text of the KItem." ) - user_groups: List[UserGroup] = Field( - [], - description="User groups able to access the KItem.", - ) custom_properties: Optional[Union[KItemCustomPropertiesModel]] = Field( None, description="Custom properties associated to the KItem" ) @@ -185,18 +181,32 @@ class KItem(KItemCompactedModel): ] = Field(None, description="DataFrame interface.") rdf_exists: bool = Field( - False, description="Whether the KItem holds an RDF Graph or not." + False, + description=( + "Whether the KItem holds an RDF Graph or not. " + "Deprecated: no longer populated by the backend." + ), + deprecated=True, ) avatar: Optional[Avatar] = Field( default_factory=Avatar, description="KItem avatar interface" ) - contexts: List[ - Union["KItem", KItemCompactedModel, KItemBaseModel] - ] = Field( - [], - description="Contextualized KItems related to this one.", + access_properties: Optional[KItemAccessProperties] = Field( + None, description="Access properties of the KItem" + ) + + contexts: List[Union["KItem", KItemCompactedModel, KItemBaseModel]] = ( + Field( + [], + description="Contextualized KItems related to this one.", + ) + ) + + schema_data: Optional[List[KItemSchemaData]] = Field( + None, + description="Semantic schema data entries associated with this KItem.", ) def __init__(self, **kwargs: "Any") -> None: @@ -238,9 +248,11 @@ def validate_annotations_before( ) -> List[Annotation]: """Validate annotations Field""" return [ - Annotation(**_make_annotation_schema(annotation)) - if isinstance(annotation, str) - else annotation + ( + Annotation(**_make_annotation_schema(annotation)) + if isinstance(annotation, str) + else annotation + ) for annotation in value ] @@ -259,9 +271,11 @@ def validate_attachments_before( ) -> List[Attachment]: """Validate attachments Field""" return [ - Attachment(name=attachment) - if isinstance(attachment, str) - else attachment + ( + Attachment(name=attachment) + if isinstance(attachment, str) + else attachment + ) for attachment in value ] @@ -287,6 +301,17 @@ def validate_apps(cls, value: List[App], info: ValidationInfo) -> AppList: app.id = kitem_id return AppList(value) + @field_validator("avatar", mode="after") + @classmethod + def validate_avatar(cls, value: Avatar, info: ValidationInfo) -> Avatar: + """ + Validate avatar Field + """ + kitem_id = info.data.get("id") + if value: + value.id = kitem_id + return value + @field_validator("linked_kitems", mode="before") @classmethod def validate_linked_kitems_list( @@ -430,6 +455,42 @@ def validate_custom_properties( cls.validate_custom_property_entry(entry, ktype) return value + @field_validator("schema_data", mode="before") + @classmethod + def _coerce_schema_data( + cls, + value: "Optional[Any]", + ) -> "Optional[Any]": + """Accept a plain dict ``{schema_id: simplified_input}`` as shorthand.""" + if isinstance(value, dict): + return [ + {"schema_id": sid, "content": {"__simplified__": data}} + for sid, data in value.items() + ] + return value + + @model_validator(mode="after") + def _resolve_schema_data_shorthands(self) -> "KItem": + """Immediately resolve any ``{schema_id: simplified_input}`` shorthands. + + After all field validators have run, the DSMS session is available via + :attr:`dsms`. Any ``schema_data`` entry whose content carries the + ``__simplified__`` sentinel is transformed to OO-LD here, so that + ``schema_data`` always contains valid OO-LD by the time the caller + receives the constructed object. + """ + if not self.schema_data: + return self + pending = [ + (entry.schema_id, entry.content["__simplified__"]) + for entry in self.schema_data + if isinstance(entry.content, dict) + and "__simplified__" in entry.content + ] + for schema_id, input_data in pending: + self.populate_schema(schema_id, input_data) + return self + @field_validator("contexts") def _validate_contexts( cls, @@ -613,16 +674,12 @@ def validate_custom_property_entry( ) and entry.value is not None ): - error_message = ( - """Value `{}` is not a valid select option. - Valid options are: """ - + str(list(choices.keys())) - + "\n" - ) if not select_options: raise ValueError( f"Widget of type `{entry.type}` does not have select options." ) + error_message = """Value `{}` is not a valid select option. + Valid options are: """ + str(list(choices.keys())) + "\n" if isinstance(entry.value, str): if entry.value not in choices: raise ValueError(error_message.format(entry.value)) @@ -741,13 +798,11 @@ def validate_custom_property_entry( if is_updated: entry.value = kitems else: - warnings.warn( - """ + warnings.warn(""" Strict validation is disabled. Will not strictly type check the custom properties. This also will take place when values are re-assigned. - """ - ) + """) return entry @@ -795,3 +850,77 @@ def is_a(self, to_be_compared: KType) -> bool: def refresh(self) -> None: """Refresh the KItem""" _refresh_kitem(self) + + def populate_schema( + self, schema_id: str, input_data: "Dict[str, Any]" + ) -> "KItem": + """Populate a semantic schema instance on this KItem. + + Looks up *schema_id* in the k-type spec's ``resolved_semantic_schemas`` + list, fetches the schema's ``transform.simplified.jsonata`` file (if it + exists), applies the transform to *input_data* to produce an OO-LD + document, and stores the result in :attr:`schema_data`. + + For schemas **with** a ``transform.simplified.jsonata``, pass the + schema's simplified input format in *input_data* (e.g. ``test_name``, + ``specimen_iri``, ``results`` for ``characterization/tensile-test/TTO``). + + For schemas **without** a transform (e.g. ``dataset/generic/DCAT``), + pass OO-LD directly. + + Call :meth:`DSMS.add` and :meth:`DSMS.commit` afterwards to persist + the schema data to the platform. + + Args: + schema_id: Schema identifier exactly matching the ``id`` field in + the k-type spec's ``semantic_schemas`` list, e.g. + ``"characterization/tensile-test/TTO"``. + input_data: Simplified input dict (schemas with transform) or OO-LD + dict (schemas without transform). + + Returns: + ``self`` to allow method chaining. + + Raises: + ValueError: If *schema_id* is not listed in the k-type spec, or if + the k-type has no v2 spec. + RuntimeError: If the schema cannot be fetched or the transform fails. + """ + from dsms.knowledge.semantics import schema_to_oold + + ktype_v2 = self.dsms.get_v2_ktype(str(self.ktype_id)) + if not ktype_v2 or not ktype_v2.spec: + raise ValueError( + f"K-type '{self.ktype_id}' has no v2 spec. " + "Import or create the k-type spec before calling populate_schema()." + ) + + schemas = ( + ktype_v2.spec.resolved_semantic_schemas + or ktype_v2.spec.semantic_schemas + or [] + ) + schema_ref = next((s for s in schemas if s.id == schema_id), None) + if schema_ref is None: + valid = [s.id for s in schemas] + raise ValueError( + f"Schema ID '{schema_id}' is not listed in the k-type spec for " + f"'{self.ktype_id}'. Available schema IDs: {valid}" + ) + + oold_doc = schema_to_oold(schema_ref.url, input_data) + + new_entry = KItemSchemaData(schema_id=schema_id, content=oold_doc) + + if self.schema_data is None: + self.schema_data = [new_entry] + else: + existing_ids = [sd.schema_id for sd in self.schema_data] + if schema_id in existing_ids: + idx = existing_ids.index(schema_id) + self.schema_data = list(self.schema_data) + self.schema_data[idx] = new_entry + else: + self.schema_data = list(self.schema_data) + [new_entry] + + return self diff --git a/dsms/knowledge/ktype.py b/dsms/knowledge/ktype.py index 7346cc4..7daa888 100644 --- a/dsms/knowledge/ktype.py +++ b/dsms/knowledge/ktype.py @@ -2,7 +2,7 @@ import logging from datetime import datetime -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from uuid import UUID from pydantic import BaseModel, Field, field_validator @@ -185,3 +185,230 @@ def dsms(self) -> "DSMS": def session(self) -> "Session": """Getter for Session""" return Session + + +# --------------------------------------------------------------------------- +# KType v2 — semantic-spec sub-models +# --------------------------------------------------------------------------- + + +class OntologyClassSpec(BaseModel): + """A single ontology class associated with a KType.""" + + iri: str = Field(..., description="IRI of the ontology class.") + label: str = Field(..., description="Human-readable label.") + ontology: str = Field(..., description="Ontology the class belongs to.") + + +class SemanticSchemaRef(BaseModel): + """Reference to a semantic schema linked to a KType.""" + + id: str = Field(..., description="Schema identifier.") + version: str = Field(..., description="Schema version string.") + url: str = Field(..., description="URL to the schema definition.") + + +class RelationSpec(BaseModel): + """A typed relation defined on a KType.""" + + id: str = Field(..., description="Relation identifier.") + label: str = Field(..., description="Human-readable label.") + description: Optional[str] = Field( + None, description="Optional description." + ) + iri: str = Field(..., description="IRI of the relation property.") + target_k_types: List[str] = Field( + [], description="KType IDs that are valid targets for this relation." + ) + cardinality: str = Field( + "0..n", description="Cardinality string, e.g. '0..n'." + ) + required: bool = Field( + False, description="Whether the relation is required." + ) + + +class KTypeSpec(BaseModel): + """Semantic specification record for a v2 KType (ktype_spec table).""" + + ktype_id: str = Field(..., description="KType ID this spec belongs to.") + format_version: Optional[str] = Field( + None, description="Spec format version, e.g. '0.1'." + ) + spec_id: Optional[str] = Field( + None, description="$id field from the spec YAML." + ) + version: Optional[str] = Field( + None, description="Semver string, e.g. '1.0.0'." + ) + description: Optional[str] = Field(None) + abstract: Optional[bool] = Field( + None, + description="If True the KType cannot be instantiated (no KItems allowed).", + ) + context: Optional[bool] = Field( + None, description="If True the KType can act as a context anchor." + ) + context_member_types: Optional[List[str]] = Field( + None, + description="KType IDs allowed as members of a context of this type.", + ) + synonyms: Optional[List[str]] = Field(None) + extends: Optional[Union[str, List[str]]] = Field( + None, description="Parent KType ID(s) for inheritance." + ) + ontology_classes: Optional[List[OntologyClassSpec]] = Field( + None, description="Ontology classes defined directly on this KType." + ) + resolved_ontology_classes: Optional[List[OntologyClassSpec]] = Field( + None, description="Ontology classes including inherited entries." + ) + semantic_schemas: Optional[List[SemanticSchemaRef]] = Field( + None, + description="Semantic schema references defined directly on this KType.", + ) + resolved_semantic_schemas: Optional[List[SemanticSchemaRef]] = Field( + None, + description="Semantic schema references including inherited entries.", + ) + relations: Optional[List[RelationSpec]] = Field(None) + dynamic_properties: Optional[Dict[str, Any]] = Field( + None, description="Raw webform spec dict (camelCase)." + ) + tags: Optional[List[str]] = Field(None) + source_url: Optional[str] = Field( + None, description="GitHub URL if the spec was imported." + ) + has_stash: bool = Field( + False, description="Whether a pre-import stash exists for this KType." + ) + stashed_spec_version: Optional[str] = Field( + None, description="Version string from the stash, if any." + ) + created_at: Optional[datetime] = Field(None) + updated_at: Optional[datetime] = Field(None) + + +class KTypeV2(KType): + """KType returned by the v2 knowledge-type-service endpoints. + + Extends the base KType with an optional semantic spec. + """ + + spec: Optional[KTypeSpec] = Field( + None, + description="Semantic specification of this KType. None for v1-only KTypes.", + ) + + +# --------------------------------------------------------------------------- +# KType v2 — request models +# --------------------------------------------------------------------------- + + +class CreateKTypeRequest(BaseModel): + """Request body for POST /v2/ktypes/ — create or upgrade a KType.""" + + id: str = Field( + ..., + description="KType ID (slug, e.g. 'my-ktype').", + min_length=2, + max_length=60, + pattern=r"^[a-z][a-z0-9-]*$", + ) + name: str = Field(..., min_length=2, max_length=100) + format_version: str = Field("0.1") + version: str = Field("1.0.0") + description: Optional[str] = Field(None) + abstract: bool = Field(False) + context: bool = Field(False) + context_member_types: Optional[List[str]] = Field(None) + synonyms: Optional[List[str]] = Field(None) + extends: Optional[Union[str, List[str]]] = Field(None) + ontology_classes: Optional[List[OntologyClassSpec]] = Field(None) + semantic_schemas: Optional[List[SemanticSchemaRef]] = Field(None) + relations: Optional[List[RelationSpec]] = Field(None) + dynamic_properties: Optional[Dict[str, Any]] = Field(None) + tags: Optional[List[str]] = Field(None) + + +class ImportFromUrlRequest(BaseModel): + """Request body for POST /v2/ktypes/import — import a spec from a GitHub URL.""" + + url: str = Field(..., description="URL to the raw ktype.yaml on GitHub.") + + +class KTypeSpecPayload(BaseModel): + """Request body for PUT /v2/ktypes/{ktype_id} — partial spec update.""" + + name: Optional[str] = Field(None, min_length=2, max_length=100) + version: Optional[str] = Field(None) + description: Optional[str] = Field(None) + abstract: Optional[bool] = Field(None) + context: Optional[bool] = Field(None) + context_member_types: Optional[List[str]] = Field(None) + synonyms: Optional[List[str]] = Field(None) + extends: Optional[Union[str, List[str]]] = Field(None) + ontology_classes: Optional[List[OntologyClassSpec]] = Field(None) + semantic_schemas: Optional[List[SemanticSchemaRef]] = Field(None) + relations: Optional[List[RelationSpec]] = Field(None) + dynamic_properties: Optional[Dict[str, Any]] = Field(None) + tags: Optional[List[str]] = Field(None) + + +# --------------------------------------------------------------------------- +# KType v2 — response models +# --------------------------------------------------------------------------- + + +class RemoteKTypeSummary(BaseModel): + """Summary of a KType available in the remote GitHub repository.""" + + id: str + name: str + remote_version: str + url: str + status: str = Field( + ..., + description="One of: 'not_imported', 'up_to_date', 'update_available'.", + ) + db_version: Optional[str] = Field(None) + + +class RemoteKTypeVersion(BaseModel): + """A tagged version of a KType in the remote repository.""" + + tag: str + version: str + url: str + + +class RemoteSchemaVersionInfo(BaseModel): + """Version entry for a remote semantic schema.""" + + version: str + url: str + + +class RemoteSchemaInfo(BaseModel): + """A semantic schema available in the remote repository.""" + + id: str + versions: List[RemoteSchemaVersionInfo] = Field([]) + + +class SpecDiffField(BaseModel): + """One changed field in a remote-diff result.""" + + field: str + local: Any + remote: Any + + +class RemoteDiffOut(BaseModel): + """Result of comparing a local KType spec against the latest remote version.""" + + remote_version: str + remote_url: str + changed_fields: List[SpecDiffField] = Field([]) + identical: bool diff --git a/dsms/knowledge/properties/__init__.py b/dsms/knowledge/properties/__init__.py index 1c0c3e8..03a152c 100644 --- a/dsms/knowledge/properties/__init__.py +++ b/dsms/knowledge/properties/__init__.py @@ -7,12 +7,12 @@ Annotation, AnnotationList, ) +from dsms.knowledge.properties.access import KItemAccessProperties from dsms.knowledge.properties.apps import App, AppList from dsms.knowledge.properties.authors import Author from dsms.knowledge.properties.contacts import ContactInfo from dsms.knowledge.properties.dataframe import Column, DataFrameContainer from dsms.knowledge.properties.summary import Summary -from dsms.knowledge.properties.user_groups import UserGroup from dsms.knowledge.properties.attachments import ( # isort:skip Attachment, @@ -30,6 +30,10 @@ ) from dsms.knowledge.properties.avatar import Avatar # isort:skip +from dsms.knowledge.properties.schema_data import ( # isort:skip + KItemSchemaData, + KItemSchemaDataList, +) __all__ = [ "Annotation", @@ -44,9 +48,11 @@ "ContactInfo", "ExternalLink", "Affiliation", - "UserGroup", "Summary", "DataFrameContainer", "Column", "KItemRelationshipModel", + "KItemAccessProperties", + "KItemSchemaData", + "KItemSchemaDataList", ] diff --git a/dsms/knowledge/properties/access.py b/dsms/knowledge/properties/access.py new file mode 100644 index 0000000..4cd1e1f --- /dev/null +++ b/dsms/knowledge/properties/access.py @@ -0,0 +1,198 @@ +"""KItem Access Property Module""" + +from enum import Enum, auto +from typing import Dict, List, Literal + +from pydantic import BaseModel, Field, field_serializer, field_validator + +from dsms.core.session import Session +from dsms.knowledge.utils import print_model + + +class OperationType(str, Enum): + """Operation Types Enum""" + + CREATE = "create" + READ = "read" + UPDATE = "update" + DELETE = "delete" + MANAGE = "manage" + + +class Role(int, Enum): + """Role Enum""" + + MEMBER = auto() + CONTRIBUTOR = auto() + OWNER = auto() + + +class RoleMapping(List[OperationType], Enum): + """Role Mapping Enum""" + + OWNER = [ + OperationType.READ, + OperationType.UPDATE, + OperationType.DELETE, + OperationType.MANAGE, + ] + MEMBER = [OperationType.READ] + CONTRIBUTOR = [OperationType.READ, OperationType.UPDATE] + + @classmethod + def get_operations(cls, role: Role) -> List[OperationType]: + """Get operations for a role""" + return getattr(cls, role.name.upper()) + + @classmethod + def min_access_level(cls, operation: OperationType) -> Role: + """Get minimum role required for an operation. + + Raises ValueError if no role grants the given operation (e.g. CREATE). + """ + candidates = [ + role.value + for role in Role + if operation in cls.get_operations(role) + ] + if not candidates: + valid = [ + op.value + for op in OperationType + if any(op in cls.get_operations(r) for r in Role) + ] + raise ValueError( + f"No role grants the '{operation.value}' operation. " + f"Valid operations are: {valid}" + ) + return Role(min(candidates)) + + @classmethod + def max_access_level(cls, operation: OperationType) -> Role: + """Get maximum role required for an operation. + + Raises ValueError if no role grants the given operation (e.g. CREATE). + """ + candidates = [ + role.value + for role in Role + if operation in cls.get_operations(role) + ] + if not candidates: + valid = [ + op.value + for op in OperationType + if any(op in cls.get_operations(r) for r in Role) + ] + raise ValueError( + f"No role grants the '{operation.value}' operation. " + f"Valid operations are: {valid}" + ) + return Role(max(candidates)) + + +class AccessGrant(BaseModel): + """A single principal (user or group) grant on a KItem.""" + + id: str = Field( + ..., + description="The unique identifier of the user or group.", + ) + type: Literal["user", "group"] = Field( + ..., + description="Whether this grant is for a user or a group.", + ) + role: Role = Field( + ..., + description="Defines the role mapping for access control.", + ) + + @field_validator("role", mode="before") + @classmethod + def parse_role(cls, v): + """Accept string names (e.g. 'owner') or integer values.""" + if isinstance(v, str): + return Role[v.upper()] + if isinstance(v, int): + return Role(v) + return v + + @property + def access_level(self) -> List[OperationType]: + """Operations granted by this role.""" + return RoleMapping.get_operations(self.role) + + @field_serializer("role") + def serialize_role_json(self, value: Role, _info): + """Serialize role as uppercase string in Python mode, lowercase for JSON.""" + if _info.mode == "python": + return value.name + return value.name.lower() + + +class KItemAccessProperties(BaseModel): + """KItem Access Properties Model""" + + visibility: Literal["private", "internal", "public"] = Field( + "private", + description=( + "Visibility level: private (explicit access only), " + "internal (all authenticated users), public (everyone)." + ), + ) + grants: List[AccessGrant] = Field( + default_factory=list, + description="Unified list of user and group access grants.", + ) + + def __str__(self) -> str: + """Pretty print the access properties fields""" + return print_model( + self, + "access_properties", + exclude_extra=Session.dsms.config.hide_properties, + ) + + def __repr__(self) -> str: + return str(self) + + @field_validator("grants", mode="after") + @classmethod + def check_duplicates(cls, v): + """Ensure no duplicate principal (id, type) pairs.""" + if v is None: + return [] + seen: set = set() + for item in v: + key = (item.id, item.type) + if key in seen: + raise ValueError(f"Duplicate grant found: {key}") + seen.add(key) + return v + + @property + def by_id(self) -> Dict[str, "AccessGrant"]: + """Get grants indexed by principal ID.""" + return {g.id: g for g in self.grants} + + @property + def by_role(self) -> Dict[Role, List[str]]: + """Get principal IDs grouped by role.""" + role_dict: Dict[Role, List[str]] = {} + for g in self.grants: + if g.role not in role_dict: + role_dict[g.role] = [] + role_dict[g.role].append(g.id) + return role_dict + + @property + def operation_by_principal(self) -> Dict[OperationType, List[str]]: + """Get principal IDs grouped by operation type.""" + op_dict: Dict[OperationType, List[str]] = {} + for g in self.grants: + for operation in g.access_level: + if operation not in op_dict: + op_dict[operation] = [] + if g.id not in op_dict[operation]: + op_dict[operation].append(g.id) + return op_dict diff --git a/dsms/knowledge/properties/apps.py b/dsms/knowledge/properties/apps.py index 114bcb2..5ba6208 100644 --- a/dsms/knowledge/properties/apps.py +++ b/dsms/knowledge/properties/apps.py @@ -125,9 +125,9 @@ def run( """ kwargs["kitem_id"] = str(self.id) if expose_sdk_config: - kwargs[ - "access_token" - ] = Session.dsms.config.token.get_secret_value() + kwargs["access_token"] = ( + Session.dsms.config.token.get_secret_value() + ) response = _perform_request( Session.dsms, diff --git a/dsms/knowledge/properties/contacts.py b/dsms/knowledge/properties/contacts.py index 657c56e..864e56e 100644 --- a/dsms/knowledge/properties/contacts.py +++ b/dsms/knowledge/properties/contacts.py @@ -1,6 +1,5 @@ """Contacts property of a KItem""" - from typing import Optional from uuid import UUID diff --git a/dsms/knowledge/properties/dataframe.py b/dsms/knowledge/properties/dataframe.py index 60da15e..b3b96a7 100644 --- a/dsms/knowledge/properties/dataframe.py +++ b/dsms/knowledge/properties/dataframe.py @@ -1,4 +1,5 @@ """DataFrame property of a KItem""" + import logging from typing import TYPE_CHECKING from uuid import UUID diff --git a/dsms/knowledge/properties/linked_kitems.py b/dsms/knowledge/properties/linked_kitems.py index aec1078..c82648a 100644 --- a/dsms/knowledge/properties/linked_kitems.py +++ b/dsms/knowledge/properties/linked_kitems.py @@ -26,7 +26,6 @@ def fetch(self) -> "KItem": class KItemRelationshipModel(BaseModel): - """Data model for a relation between two linked KItems""" is_incoming: bool = Field( diff --git a/dsms/knowledge/properties/schema_data.py b/dsms/knowledge/properties/schema_data.py new file mode 100644 index 0000000..67aa353 --- /dev/null +++ b/dsms/knowledge/properties/schema_data.py @@ -0,0 +1,29 @@ +"""KItem Schema Data property model""" + +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, Field + + +class KItemSchemaData(BaseModel): + """Holds the semantic schema data associated with a KItem. + + Each entry maps a schema ID (e.g. an ontology class IRI) to a free-form + content dict that stores the instance data for that schema. + """ + + schema_id: str = Field( + ..., description="Schema identifier (e.g. an ontology class IRI)." + ) + content: Optional[Dict[str, Any]] = Field( + None, description="Instance data for the schema." + ) + + +class KItemSchemaDataList(List[KItemSchemaData]): + """Typed list of KItemSchemaData entries with lookup by schema_id.""" + + @property + def by_schema_id(self) -> "Dict[str, KItemSchemaData]": + """Return a dict keyed by schema_id.""" + return {entry.schema_id: entry for entry in self} diff --git a/dsms/knowledge/search.py b/dsms/knowledge/search.py index 575092e..6409367 100644 --- a/dsms/knowledge/search.py +++ b/dsms/knowledge/search.py @@ -17,10 +17,11 @@ class KItemSearchResult(BaseModel): kitem: Union["KItem", "KItemCompactedModel"] = Field( ..., description="KItem returned by the search" ) - fuzzy: Union[bool, float] = Field( + fuzzy: Union[bool, float, str] = Field( ..., description="""Whether the KItem was found through a similarity hit. - If not a bool, a float indicates the distance from search term""", + If not a bool, a float indicates the distance from search term. + The string 'id' indicates the KItem was found by exact UUID lookup.""", ) def __str__(self): diff --git a/dsms/knowledge/semantics/__init__.py b/dsms/knowledge/semantics/__init__.py index 3c31da6..786d284 100644 --- a/dsms/knowledge/semantics/__init__.py +++ b/dsms/knowledge/semantics/__init__.py @@ -1 +1,60 @@ -"""DSMS Semantics Module""" +"""DSMS Semantics Module + +Exposes :func:`schema_to_oold`, used by :meth:`KItem.populate_schema` to +convert simplified input dicts to OO-LD documents via the semantic schema +transforms defined in the k-type spec. +""" + +import logging +from typing import Any, Dict + +import requests + +logger = logging.getLogger(__name__) + + +def schema_to_oold( + schema_url: str, input_data: Dict[str, Any] +) -> Dict[str, Any]: + """Convert a simplified input dict to an OO-LD document. + + Fetches ``specs/transform.simplified.jsonata`` from *schema_url* (a raw + GitHub base URL) and applies the JSONata transform to *input_data*. If + the transform file does not exist (HTTP 404), *input_data* is returned + as-is — the caller is expected to pass OO-LD directly in that case. + + Args: + schema_url: Raw GitHub URL pointing to the schema folder, as stored + in the k-type spec's ``semantic_schemas[].url`` field. + input_data: Simplified input dict (for schemas with a transform) or an + OO-LD dict (for schemas without a transform). + + Returns: + OO-LD document dict ready to be stored as ``KItemSchemaData.content``. + + Raises: + ImportError: If the ``semantic-schemas`` package is not installed. + requests.HTTPError: If fetching the transform file fails (non-404). + """ + try: + from jsonata.jsonata import Jsonata + except ImportError as exc: + raise ImportError( + "The 'semantic-schemas' package is required to apply simplified " + "input transforms. Install it with: pip install semantic-schemas" + ) from exc + + transform_url = ( + f"{schema_url.rstrip('/')}/specs/transform.simplified.jsonata" + ) + response = requests.get(transform_url, timeout=30) + + if response.status_code == 404: + logger.debug( + "No simplified transform found at %s — treating input as OO-LD", + schema_url, + ) + return input_data + + response.raise_for_status() + return Jsonata(response.text).evaluate(input_data) diff --git a/dsms/knowledge/semantics/units/utils.py b/dsms/knowledge/semantics/units/utils.py index 66599cf..e2e2a61 100644 --- a/dsms/knowledge/semantics/units/utils.py +++ b/dsms/knowledge/semantics/units/utils.py @@ -116,15 +116,11 @@ def get_property_unit( 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.""" - ) + 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.""" - ) + raise ValueError(f"""Property `{property_name}` owns more than one + unit with respect to the semantics applied.""") unit = query.results.pop() else: unit = measurement_unit.model_dump() diff --git a/dsms/knowledge/sparql_interface/sparql_interface.py b/dsms/knowledge/sparql_interface/sparql_interface.py index 7ffff07..9e01d79 100644 --- a/dsms/knowledge/sparql_interface/sparql_interface.py +++ b/dsms/knowledge/sparql_interface/sparql_interface.py @@ -6,7 +6,9 @@ from dsms.knowledge.sparql_interface.subgraph import Subgraph from dsms.knowledge.sparql_interface.utils import ( _add_rdf, + _graph_query_context, _sparql_query, + _sparql_query_context, _sparql_update, ) @@ -17,7 +19,6 @@ class SparqlInterface: - """Sparql Interface for the DSMS.""" def __init__(self, dsms): @@ -57,6 +58,14 @@ def insert( repository, ) + def query_context(self, context_id: str, query: str) -> "Dict[str, Any]": + """Perform a SPARQL query scoped to a context KItem.""" + return _sparql_query_context(self._dsms, context_id, query) + + def graph_context(self, context_id: str, query: str) -> "Dict[str, Any]": + """Perform a graph query scoped to a context KItem.""" + return _graph_query_context(self._dsms, context_id, query) + @property def subgraph(self) -> Subgraph: """Subgraph interface for DSMS""" diff --git a/dsms/knowledge/sparql_interface/utils.py b/dsms/knowledge/sparql_interface/utils.py index 08cbab8..dae15db 100644 --- a/dsms/knowledge/sparql_interface/utils.py +++ b/dsms/knowledge/sparql_interface/utils.py @@ -1,4 +1,5 @@ """Sparql interface utilities for the DSMS""" + import io from typing import TYPE_CHECKING @@ -141,6 +142,54 @@ def _update_subgraph( _create_subgraph(dsms, graph, encoding, repository) +def _sparql_query_context( + dsms: "DSMS", context_id: str, query: str +) -> "Dict[str, Any]": + """Submit a SPARQL query scoped to a context KItem.""" + response = _perform_request( + dsms, + "api/knowledge/sparql/context", + "post", + data={"query": query}, + params={"context_id": context_id}, + ) + if not response.ok: + raise RuntimeError( + f"Context SPARQL query was not successful: {response.text}" + ) + try: + response = response.json() + except Exception as excep: + raise RuntimeError( + f"Something went wrong parsing context SPARQL response: `{query}`" + ) from excep + return response + + +def _graph_query_context( + dsms: "DSMS", context_id: str, query: str +) -> "Dict[str, Any]": + """Submit a graph query scoped to a context KItem.""" + response = _perform_request( + dsms, + "api/knowledge/graph/context", + "post", + json={"query": query}, + params={"context_id": context_id}, + ) + if not response.ok: + raise RuntimeError( + f"Context graph query was not successful: {response.text}" + ) + try: + response = response.json() + except Exception as excep: + raise RuntimeError( + f"Something went wrong parsing context graph response: `{query}`" + ) from excep + return response + + def _get_subgraph( dsms: "DSMS", identifier: str, repository: str, is_kitem_id: bool = False ) -> "Graph": diff --git a/dsms/knowledge/utils.py b/dsms/knowledge/utils.py index 40de85c..d9c7b7b 100644 --- a/dsms/knowledge/utils.py +++ b/dsms/knowledge/utils.py @@ -1,4 +1,5 @@ """DSMS knowledge utilities""" + import base64 import io import logging @@ -9,7 +10,7 @@ import warnings from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union from uuid import UUID import oyaml as yaml @@ -31,6 +32,7 @@ from dsms.apps import AppConfig from dsms.core.session import Buffers from dsms.knowledge import KItem, KType, ProcessSchema, WebformSchema + from dsms.knowledge.groups import User from dsms.knowledge.properties import Attachment logger = logging.getLogger(__name__) @@ -93,15 +95,9 @@ def dump_model(self, exclude_extra: set = set()) -> Dict[str, Any]: Dict[str, Any]: A dictionary of the model fields with specified exclusions. """ exclude = self.model_config.get("exclude", set()) | exclude_extra - dumped = self.model_dump( - exclude_none=True, - exclude_unset=True, - exclude=exclude, + return self.model_dump( + exclude_none=True, exclude_unset=True, exclude=exclude, mode="python" ) - return { - key: (str(value) if isinstance(value, UUID) else value) - for key, value in dumped.items() - } def print_ktype(self) -> str: @@ -206,10 +202,8 @@ def _get_ktype( response = _perform_request(dsms, f"api/knowledge-type/{ktype_id}", "get") if response.status_code == 404 and raise_error: - raise ValueError( - f"""KType with the id `{ktype_id}` does not exist in - DSMS-instance `{dsms.config.host_url}`""" - ) + raise ValueError(f"""KType with the id `{ktype_id}` does not exist in + DSMS-instance `{dsms.config.host_url}`""") if not response.ok and raise_error: raise ValueError( f"""An error occured fetching the KType with id `{ktype_id}`: @@ -262,18 +256,24 @@ def _delete_ktype(ktype: "KType") -> None: _get_remote_ktypes(ktype.dsms) -def _get_kitem_list(dsms: "DSMS", limit=10, offset=0) -> "KItemListModel": +def _get_kitem_list( + dsms: "DSMS", + user_id: Optional[str] = None, + limit=10, + offset=0, + name: Optional[str] = None, +) -> "KItemListModel": """Get all available KItems from the remote backend.""" from dsms.knowledge.kitem import KItem # isort:skip + params = {"user_id": user_id, "limit": limit, "offset": offset} + if name is not None: + params["name"] = name response = _perform_request( dsms, "api/knowledge/kitems", "get", - params={ - "limit": limit, - "offset": offset, - }, + params=params, ) if not response.ok: raise ValueError( @@ -307,10 +307,8 @@ def _get_kitem( response = _perform_request(dsms, f"api/knowledge/kitems/{uuid}", "get") if response.status_code == 404 and raise_error: - raise ValueError( - f"""KItem with uuid `{uuid}` does not exist in - DSMS-instance `{dsms.config.host_url}`""" - ) + raise ValueError(f"""KItem with uuid `{uuid}` does not exist in + DSMS-instance `{dsms.config.host_url}`""") if not response.ok and raise_error: raise ValueError( f"""An error occured fetching the KItem with uuid `{uuid}`: @@ -333,6 +331,10 @@ def _create_new_kitem(kitem: "KItem") -> "Dict[str, Any]": "slug": kitem.slug, "ktype_id": kitem.ktype.id, } + if kitem.access_properties is not None: + access = kitem.access_properties.model_dump(mode="json") + payload["visibility"] = access.pop("visibility", "private") + payload["access_properties"] = access logger.debug("Create new KItem with payload: %s", payload) response = _perform_request( kitem.dsms, "api/knowledge/kitems", "post", json=payload @@ -357,7 +359,6 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: "rdf_exists", "in_backend", "avatar_exists", - "user_groups", "ktype_id", "attachments", "id", @@ -368,6 +369,7 @@ def _update_kitem(new_kitem: "KItem", old_kitem: "Dict[str, Any]") -> Response: "contexts", }, exclude_defaults=True, + mode="json", ) payload.update( **differences, @@ -442,10 +444,8 @@ def _upload_attachments(kitem: "KItem", attachment: "Attachment") -> None: 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)}""" - ) + raise TypeError(f"""Invalid content type of attachment with name + `{attachment.name}`: {type(attachment.content)}""") file.name = attachment.name upload_file = {"dataFile": file} response = _perform_request( @@ -595,11 +595,8 @@ def _get_kitems_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): differences = {} attributes = [ ("annotations", ("annotations", "link", "unlink")), - ("user_groups", ("user_groups", "add", "remove")), ] - to_compare = kitem_new.model_dump( - include={"annotations", "user_groups", "contexts"} - ) + to_compare = kitem_new.model_dump(include={"annotations", "contexts"}) for name, terms in attributes: to_add_name = terms[0] + "_to_" + terms[1] to_remove_name = terms[0] + "_to_" + terms[2] @@ -621,6 +618,22 @@ def _get_kitems_diffs(kitem_old: "Dict[str, Any]", kitem_new: "KItem"): context_kitems = _get_kitem_contexts(kitem_old, kitem_new) # same holds for kitem apps apps = _get_apps_diff(kitem_old, kitem_new) + # access_properties: include as full replacement when changed. + # visibility is a top-level field on the backend update model, so it must + # be sent separately rather than nested inside access_properties. + old_access = kitem_old.get("access_properties") + new_access = ( + kitem_new.access_properties.model_dump(mode="json") + if kitem_new.access_properties is not None + else None + ) + if new_access != old_access and new_access is not None: + visibility = new_access.pop("visibility", None) + if visibility is not None: + old_visibility = (old_access or {}).get("visibility") + if visibility != old_visibility: + differences["visibility"] = visibility + differences["access_properties"] = new_access # merge with previously found differences differences.update(**linked_kitems, **apps, **context_kitems) return differences @@ -682,6 +695,8 @@ def _commit(buffers: "Buffers") -> None: _delete_dataframe(obj.id) _update_kitem(obj, old_kitem) _update_attachments(obj, old_kitem) + if obj.schema_data is not None: + _update_schema_data(obj, old_kitem) if obj.avatar.file or obj.avatar.encode_qr: _commit_avatar(obj) elif isinstance(obj, KType) or ( @@ -867,6 +882,162 @@ def _refresh_kitem(kitem: "KItem") -> None: kitem.dataframe = [{"id": kitem.id, **column} for column in dataframe] +def _get_ktypes_by_parent( + dsms: "DSMS", parent_id: str +) -> List[Dict[str, Any]]: + """Return KTypes whose spec.extends chain contains parent_id.""" + response = _perform_request( + dsms, "api/knowledge-type/", "get", params={"parent": parent_id} + ) + if not response.ok: + raise ValueError( + f"Failed to fetch ktypes by parent `{parent_id}`: {response.text}" + ) + return response.json() + + +# --------------------------------------------------------------------------- +# KType v2 utilities (api/knowledge-type/v2/ktypes/…) +# --------------------------------------------------------------------------- + +_V2_BASE = "api/knowledge-type/v2/ktypes" + + +def _v2_list_ktypes(dsms: "DSMS") -> List[Dict[str, Any]]: + """GET /v2/ktypes/ — list all v2 KTypes.""" + response = _perform_request(dsms, f"{_V2_BASE}/", "get") + if not response.ok: + raise ValueError(f"Failed to list v2 ktypes: {response.text}") + return response.json() + + +def _v2_get_ktype(dsms: "DSMS", ktype_id: str) -> Dict[str, Any]: + """GET /v2/ktypes/{ktype_id} — fetch a single v2 KType.""" + response = _perform_request(dsms, f"{_V2_BASE}/{ktype_id}", "get") + if not response.ok: + raise ValueError( + f"Failed to fetch v2 ktype `{ktype_id}`: {response.text}" + ) + return response.json() + + +def _v2_create_ktype(dsms: "DSMS", payload: Dict[str, Any]) -> Dict[str, Any]: + """POST /v2/ktypes/ — create or upgrade a v2 KType.""" + response = _perform_request(dsms, f"{_V2_BASE}/", "post", json=payload) + if not response.ok: + raise ValueError(f"Failed to create v2 ktype: {response.text}") + return response.json() + + +def _v2_import_ktype(dsms: "DSMS", url: str) -> Dict[str, Any]: + """POST /v2/ktypes/import — import a KType spec from a GitHub URL.""" + response = _perform_request( + dsms, f"{_V2_BASE}/import", "post", json={"url": url} + ) + if not response.ok: + raise ValueError( + f"Failed to import v2 ktype from `{url}`: {response.text}" + ) + return response.json() + + +def _v2_update_ktype( + dsms: "DSMS", ktype_id: str, payload: Dict[str, Any] +) -> Dict[str, Any]: + """PUT /v2/ktypes/{ktype_id} — partial spec update.""" + response = _perform_request( + dsms, f"{_V2_BASE}/{ktype_id}", "put", json=payload + ) + if not response.ok: + raise ValueError( + f"Failed to update v2 ktype `{ktype_id}`: {response.text}" + ) + return response.json() + + +def _v2_delete_ktype(dsms: "DSMS", ktype_id: str) -> None: + """DELETE /v2/ktypes/{ktype_id} — delete a v2 KType.""" + response = _perform_request(dsms, f"{_V2_BASE}/{ktype_id}", "delete") + if not response.ok: + raise ValueError( + f"Failed to delete v2 ktype `{ktype_id}`: {response.text}" + ) + + +def _v2_restore_stash(dsms: "DSMS", ktype_id: str) -> Dict[str, Any]: + """POST /v2/ktypes/{ktype_id}/restore-stash — restore pre-import stash.""" + response = _perform_request( + dsms, f"{_V2_BASE}/{ktype_id}/restore-stash", "post" + ) + if not response.ok: + raise ValueError( + f"Failed to restore stash for v2 ktype `{ktype_id}`: {response.text}" + ) + return response.json() + + +def _v2_refresh_ktype(dsms: "DSMS", ktype_id: str) -> Dict[str, Any]: + """POST /v2/ktypes/{ktype_id}/refresh — re-fetch spec from source URL.""" + response = _perform_request(dsms, f"{_V2_BASE}/{ktype_id}/refresh", "post") + if not response.ok: + raise ValueError( + f"Failed to refresh v2 ktype `{ktype_id}`: {response.text}" + ) + return response.json() + + +def _v2_export_ktype(dsms: "DSMS", ktype_id: str) -> str: + """GET /v2/ktypes/{ktype_id}/export — download ktype.yaml as text.""" + response = _perform_request(dsms, f"{_V2_BASE}/{ktype_id}/export", "get") + if not response.ok: + raise ValueError( + f"Failed to export v2 ktype `{ktype_id}`: {response.text}" + ) + return response.text + + +def _v2_list_remote_ktypes(dsms: "DSMS") -> List[Dict[str, Any]]: + """GET /v2/ktypes/remote — list KTypes available in the remote repo.""" + response = _perform_request(dsms, f"{_V2_BASE}/remote", "get") + if not response.ok: + raise ValueError(f"Failed to list remote v2 ktypes: {response.text}") + return response.json() + + +def _v2_list_remote_schemas(dsms: "DSMS") -> List[Dict[str, Any]]: + """GET /v2/ktypes/remote/schemas — list semantic schemas in the remote repo.""" + response = _perform_request(dsms, f"{_V2_BASE}/remote/schemas", "get") + if not response.ok: + raise ValueError(f"Failed to list remote schemas: {response.text}") + return response.json() + + +def _v2_list_remote_versions( + dsms: "DSMS", ktype_id: str +) -> List[Dict[str, Any]]: + """GET /v2/ktypes/{ktype_id}/remote-versions — list GitHub tags for a KType.""" + response = _perform_request( + dsms, f"{_V2_BASE}/{ktype_id}/remote-versions", "get" + ) + if not response.ok: + raise ValueError( + f"Failed to list remote versions for v2 ktype `{ktype_id}`: {response.text}" + ) + return response.json() + + +def _v2_remote_diff(dsms: "DSMS", ktype_id: str) -> Dict[str, Any]: + """GET /v2/ktypes/{ktype_id}/remote-diff — diff local vs latest remote spec.""" + response = _perform_request( + dsms, f"{_V2_BASE}/{ktype_id}/remote-diff", "get" + ) + if not response.ok: + raise ValueError( + f"Failed to get remote diff for v2 ktype `{ktype_id}`: {response.text}" + ) + return response.json() + + def _refresh_ktype(ktype: "KType") -> None: """Refresh the KItem""" for key, value in _get_ktype(ktype.dsms, ktype.id, as_json=True).items(): @@ -901,6 +1072,9 @@ def _search( offset: "Optional[int]" = 0, allow_fuzzy: "Optional[bool]" = True, compact: "Optional[bool]" = False, + contexts: "Optional[List[str]]" = None, + attachment_extensions: "Optional[List[str]]" = None, + visibility: Optional[Literal["private", "internal", "public"]] = None, ) -> "List[SearchResult]": """Search for KItems in the remote backend""" from dsms import KItem, KItemCompactedModel @@ -916,12 +1090,19 @@ def _search( "offset": offset, "compact": compact, } + if contexts is not None: + payload["contexts"] = contexts + if visibility is not None: + payload["visibility"] = visibility + params = {"allow_fuzzy": allow_fuzzy} + if attachment_extensions is not None: + params["attachment_extensions"] = attachment_extensions response = _perform_request( dsms, "api/knowledge/kitems/search", "post", json=payload, - params={"allow_fuzzy": allow_fuzzy}, + params=params, ) if not response.ok: raise RuntimeError( @@ -936,9 +1117,11 @@ def _search( return SearchResult( hits=[ { - "kitem": KItemCompactedModel(**item.get("kitem")) - if compact - else KItem(**item.get("kitem")), + "kitem": ( + KItemCompactedModel(**item.get("kitem")) + if compact + else KItem(**item.get("kitem")) + ), "fuzzy": item.get("fuzzy"), } for item in dumped.get("hits") @@ -1089,9 +1272,9 @@ def _make_avatar( return avatar -def _get_avatar(dsms: "DSMS", kitem_id: UUID) -> Image.Image: +def _get_avatar(kitem_id: UUID) -> Image.Image: response = _perform_request( - dsms, f"api/knowledge/avatar/{kitem_id}", "get" + Session.dsms, f"api/knowledge/avatar/{kitem_id}", "get" ) buffer = io.BytesIO(response.content) return Image.open(buffer) @@ -1170,7 +1353,7 @@ def _transform_custom_properties_schema(custom_properties: Any, webform: Any): def _transform_from_flat_schema( - custom_properties: Dict[str, Any] + custom_properties: Dict[str, Any], ) -> Dict[str, Any]: return {"sections": [_make_misc_section(custom_properties)]} @@ -1430,16 +1613,18 @@ def generate_mapping(ktype_id: str, webform: dict): "relation_type": relation_mapping_extra.get( "type" ), - "object_type": { - "suffix": "max", - "iri": relation_mapping_extra.get( - "classIri" - ), - **unit, - } - if relation_mapping_extra.get("type") - == "object_property" - else object_type, + "object_type": ( + { + "suffix": "max", + "iri": relation_mapping_extra.get( + "classIri" + ), + **unit, + } + if relation_mapping_extra.get("type") + == "object_property" + else object_type + ), } ) @@ -1462,3 +1647,257 @@ def generate_mapping(ktype_id: str, webform: dict): else: mapping = mappings return mapping + + +def _get_user_groups(dsms: "DSMS"): + """Fetch all user groups from the DSMS backend.""" + from dsms.knowledge.groups import Group, GroupList + + response = _perform_request( + dsms, + "api/users/groups", + "get", + ) + if not response.ok: + raise ConnectionError(f"Failed to fetch user groups: {response.text}") + groups = response.json() + return GroupList([Group(**group) for group in groups]) + + +def _get_group_members(dsms: "DSMS", group_id: str) -> List[Dict[str, Any]]: + """Fetch members of a specific group from the DSMS backend.""" + from dsms.knowledge.groups import User + + response = _perform_request( + dsms, + f"api/users/groups/{group_id}/members", + "get", + ) + if not response.ok: + raise ConnectionError( + f"Failed to fetch members of group {group_id}: {response.text}" + ) + return [User(**u) for u in response.json()] + + +def _create_group( + dsms: "DSMS", + name: str, + description: str = "", + parent_id: Optional[str] = None, +) -> Dict[str, Any]: + """Create a new group (top-level or subgroup) in the DSMS backend.""" + from dsms.knowledge.groups import Group + + if parent_id: + url = f"api/users/groups/{parent_id}/subgroups" + else: + url = "api/users/groups" + payload = {"name": name, "description": description} + response = _perform_request(dsms, url, "post", json=payload) + if not response.ok: + raise ValueError(f"Failed to create group '{name}': {response.text}") + return Group(**response.json()) + + +def _update_group( + dsms: "DSMS", + group_id: str, + name: Optional[str] = None, + description: Optional[str] = None, +) -> Dict[str, Any]: + """Update name or description of an existing group.""" + from dsms.knowledge.groups import Group + + payload = {} + if name is not None: + payload["name"] = name + if description is not None: + payload["description"] = description + if not payload: + raise ValueError( + "At least one of name or description must be provided." + ) + response = _perform_request( + dsms, f"api/users/groups/{group_id}", "put", json=payload + ) + if not response.ok: + raise ValueError(f"Failed to update group {group_id}: {response.text}") + return Group(**response.json()) + + +def _delete_group(dsms: "DSMS", group_id: str) -> None: + """Delete a group from the DSMS backend.""" + response = _perform_request(dsms, f"api/users/groups/{group_id}", "delete") + if not response.ok: + raise ValueError(f"Failed to delete group {group_id}: {response.text}") + + +def _add_group_member(dsms: "DSMS", group_id: str, user_id: str) -> None: + """Add a user to a group.""" + response = _perform_request( + dsms, + f"api/users/groups/{group_id}/members", + "post", + json={"user_id": user_id}, + ) + if not response.ok: + raise ValueError( + f"Failed to add user {user_id} to group {group_id}: {response.text}" + ) + + +def _remove_group_member(dsms: "DSMS", group_id: str, user_id: str) -> None: + """Remove a user from a group.""" + response = _perform_request( + dsms, + f"api/users/groups/{group_id}/members/{user_id}", + "delete", + ) + if not response.ok: + raise ValueError( + f"Failed to remove user {user_id} from group {group_id}: {response.text}" + ) + + +def _get_group_subgroups(dsms: "DSMS", group_id: str) -> List[Any]: + """Fetch the direct child groups of a group.""" + from dsms.knowledge.groups import Group, GroupList + + response = _perform_request( + dsms, + f"api/users/groups/{group_id}/subgroups", + "get", + ) + if not response.ok: + raise ConnectionError( + f"Failed to fetch subgroups of group {group_id}: {response.text}" + ) + return GroupList([Group(**g) for g in response.json()]) + + +def _add_group_to_group(dsms: "DSMS", parent_id: str, child_id: str) -> None: + """Link an existing group as a subgroup of another group.""" + response = _perform_request( + dsms, + f"api/users/groups/{parent_id}/subgroups/{child_id}", + "post", + ) + if not response.ok: + raise ValueError( + f"Failed to link group {child_id} under group {parent_id}: {response.text}" + ) + + +def _remove_group_from_group( + dsms: "DSMS", parent_id: str, child_id: str +) -> None: + """Detach a child group from its parent, promoting it back to top-level.""" + response = _perform_request( + dsms, + f"api/users/groups/{parent_id}/subgroups/{child_id}", + "delete", + ) + if not response.ok: + raise ValueError( + f"Failed to unlink group {child_id} from group {parent_id}: {response.text}" + ) + + +def _get_user_list(dsms: "DSMS"): + """Fetch all users from the DSMS backend.""" + from dsms.knowledge.groups import User, UserList + + response = _perform_request( + dsms, + "api/users/", + "get", + ) + if not response.ok: + raise ConnectionError(f"Failed to fetch users: {response.text}") + users = response.json() + return UserList([User(**user) for user in users]) + + +def get_user_by_id(dsms: "DSMS", user_id: str) -> "User": + """Fetch a single user by ID from the DSMS backend. + + Args: + dsms: The DSMS instance to use for the request. + user_id: The unique identifier of the user. + + Returns: + User object for the given ID. + """ + from dsms.knowledge.groups import User + + response = _perform_request( + dsms, + f"api/users/{user_id}", + "get", + ) + if not response.ok: + raise ValueError(f"Failed to fetch user {user_id}: {response.text}") + return User(**response.json()) + + +def _get_schema_data(dsms: "DSMS", kitem_id: str) -> List[Dict[str, Any]]: + """Fetch all schema-data entries for a KItem from the remote backend.""" + response = _perform_request( + dsms, f"api/knowledge/kitems/{kitem_id}/schema-data", "get" + ) + if not response.ok: + raise ValueError( + f"Failed to fetch schema data for kitem `{kitem_id}`: {response.text}" + ) + return response.json() + + +def _put_schema_data( + dsms: "DSMS", + kitem_id: str, + schema_id: str, + content: Optional[Dict[str, Any]], +) -> Dict[str, Any]: + """Create or update a single schema-data entry for a KItem.""" + response = _perform_request( + dsms, + f"api/knowledge/kitems/{kitem_id}/schema-data/{schema_id}", + "put", + json={"content": content}, + ) + if not response.ok: + raise ValueError( + f"Failed to put schema data `{schema_id}` for kitem `{kitem_id}`: {response.text}" + ) + return response.json() + + +def _delete_schema_data(dsms: "DSMS", kitem_id: str, schema_id: str) -> None: + """Delete a single schema-data entry for a KItem.""" + response = _perform_request( + dsms, + f"api/knowledge/kitems/{kitem_id}/schema-data/{schema_id}", + "delete", + ) + if not response.ok: + raise ValueError( + f"Failed to delete schema data `{schema_id}` for kitem `{kitem_id}`: {response.text}" + ) + + +def _update_schema_data(kitem: "KItem", old_kitem: Dict[str, Any]) -> None: + """Sync schema_data changes between local KItem state and the backend.""" + old_entries = { + e["schema_id"]: e.get("content") + for e in old_kitem.get("schema_data", []) + } + new_entries = {e.schema_id: e.content for e in (kitem.schema_data or [])} + + for schema_id, content in new_entries.items(): + if schema_id not in old_entries or old_entries[schema_id] != content: + _put_schema_data(kitem.dsms, str(kitem.id), schema_id, content) + + for schema_id in old_entries: + if schema_id not in new_entries: + _delete_schema_data(kitem.dsms, str(kitem.id), schema_id) diff --git a/dsms/knowledge/webform.py b/dsms/knowledge/webform.py index 96986eb..1f0aae9 100644 --- a/dsms/knowledge/webform.py +++ b/dsms/knowledge/webform.py @@ -32,7 +32,6 @@ from dsms.core.logging import handler # isort:skip - logger = logging.getLogger(__name__) logger.addHandler(handler) logger.propagate = False @@ -41,16 +40,24 @@ class Widget(Enum): """Enum for widgets""" - TEXT = "Text" - FILE = "File" - TEXTAREA = "Textarea" - NUMBER = "Number" - SLIDER = "Slider" + ARRAY_GROUP = "Array group" CHECKBOX = "Checkbox" - SELECT = "Select" - RADIO = "Radio" + DATE = "Date" + DATETIME = "Date-time" + FILE = "File" + KEY_VALUE_PAIRS = "Key-value pairs" KNOWLEDGE_ITEM = "Knowledge item" + LATEX = "LaTeX" MULTI_SELECT = "Multi-select" + NUMBER = "Number" + RADIO = "Radio" + SELECT = "Select" + SLIDER = "Slider" + STAR_RATING = "Star rating" + TEXT = "Text" + TEXTAREA = "Textarea" + URL = "URL" + VOCABULARY_SELECT = "Vocabulary select" class RelationMappingType(Enum): @@ -522,11 +529,9 @@ def __getattr__(self, key) -> Any: f"Section with name `{self.name}` has no attribute '{key}'" ) if len(target) > 1: - raise AttributeError( - f"""Section with name `{self.name}` + raise AttributeError(f"""Section with name `{self.name}` has multiple attributes '{key}'. - Please specify the concrete entry via indexing !""" - ) + Please specify the concrete entry via indexing !""") target = target.pop() else: diff --git a/scripts/run_notebooks.sh b/scripts/run_notebooks.sh new file mode 100755 index 0000000..ea17893 --- /dev/null +++ b/scripts/run_notebooks.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# run_notebooks.sh: execute and validate all tutorial notebooks +# +# Usage: +# ./scripts/run_notebooks.sh # test all notebooks +# ./scripts/run_notebooks.sh --refresh # re-execute and save outputs in-place +# ./scripts/run_notebooks.sh path/to/nb.ipynb # test a single notebook +# ./scripts/run_notebooks.sh --refresh path/to/nb.ipynb # refresh a single notebook +# +# Requirements: activate your virtual environment first. +# python -m venv .venv && source .venv/bin/activate +# pip install -e ".[docs,tests]" +# +# Environment: the notebooks connect to a live DSMS instance. +# Set credentials in a .env file or via environment variables before running: +# DSMS_HOST_URL, DSMS_USERNAME, DSMS_PASSWORD (or DSMS_TOKEN) + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TUTORIALS_DIR="$REPO_ROOT/docs/dsms_sdk/tutorials" +cd "$REPO_ROOT" + +# Collect notebooks +if [ $# -ge 1 ] && [ "$1" = "--refresh" ]; then + MODE="refresh" + if [ $# -ge 2 ]; then + NOTEBOOKS=("${@:2}") + else + mapfile -t NOTEBOOKS < <( + find "$TUTORIALS_DIR" -name "*.ipynb" \ + ! -path "*/.ipynb_checkpoints/*" \ + | sort + ) + fi +elif [ $# -ge 1 ]; then + NOTEBOOKS=("$@") + MODE="test" +else + mapfile -t NOTEBOOKS < <( + find "$TUTORIALS_DIR" -name "*.ipynb" \ + ! -path "*/.ipynb_checkpoints/*" \ + | sort + ) + MODE="test" +fi + +# Export .env variables so notebook kernels can connect without a local .env file +if [ -f "$REPO_ROOT/.env" ]; then + set -a + # shellcheck source=/dev/null + source "$REPO_ROOT/.env" + set +a +fi + +echo "Found ${#NOTEBOOKS[@]} notebook(s)." +echo "" + +if [ "$MODE" = "refresh" ]; then + echo "Mode: refresh (executing and saving outputs in-place)" + echo "" + for nb in "${NOTEBOOKS[@]}"; do + echo " Refreshing: $nb" + jupyter nbconvert \ + --to notebook \ + --execute \ + --inplace \ + --ExecutePreprocessor.timeout=300 \ + "$nb" + done + echo "" + echo "Done. Commit the updated notebooks together with any documentation changes." +else + echo "Mode: test (pytest + nbmake, outputs not saved)" + echo "" + pytest --nbmake "${NOTEBOOKS[@]}" +fi diff --git a/setup.cfg b/setup.cfg index 8b031da..523ecbd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dsms_sdk -version = v4.1.1 +version = v5.0.0 description = Python SDK core-package for working with the Dataspace Management System (DSMS). long_description = file: README.md long_description_content_type = text/markdown @@ -11,7 +11,6 @@ license = BSD-3-Clause license_files = LICENSE classifiers = Development Status :: 2 - Pre-Alpha - License :: OSI Approved :: BSD License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython @@ -23,16 +22,17 @@ install_requires = click>=8,<9 html5lib>=1,<2 lru-cache<1 - oyaml==1 - pandas>=2,<3 - pydantic>=2,<=2.11.7 - pydantic-settings + oyaml>=1 + pandas>=2,<4 + pydantic>=2,<3 + pydantic-settings>=2,<3 python-dotenv qrcode-artistic>=3,<4 - rdflib>=6,<7 + rdflib>=6,<8 requests - segno>=1.6,<1.7 -python_requires = >=3.8 + segno>=1.6,<2 + semantic-schemas>=0.7 +python_requires = >=3.10,<3.15 include_package_data = True [options.entry_points] @@ -48,6 +48,7 @@ docs = jupyter==1.0.0 myst-parser==4.0.0 nbsphinx==0.9.5 + pytest-nbmake sphinx-autobuild==2024.4.16 sphinx-book-theme==1.1.3 sphinx-copybutton==0.5.2 @@ -57,7 +58,7 @@ docs = sphinxcontrib-redoc==1.6.0 pre_commit = pre-commit==3.3.2 - pylint==3.2.0 + pylint==3.3.9 tests = pytest>=7.4.3 pytest-mock diff --git a/setup.py b/setup.py index 5948248..63e14cb 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ """This file is required for editable installs of the package.""" + from setuptools import setup setup() diff --git a/tests/conftest.py b/tests/conftest.py index bf894bc..6dfe5d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ """Conftest for DSMS-SDK""" + import json from typing import TYPE_CHECKING from urllib.parse import urljoin diff --git a/tests/test_access.py b/tests/test_access.py new file mode 100644 index 0000000..bf38813 --- /dev/null +++ b/tests/test_access.py @@ -0,0 +1,318 @@ +"""Tests for Access Property Module""" + +from typing import List + +import pytest +from pydantic import ValidationError + +from dsms.knowledge.properties.access import ( + AccessGrant, + KItemAccessProperties, + OperationType, + Role, + RoleMapping, +) + + +@pytest.fixture +def sample_user_grants() -> List[AccessGrant]: + return [ + AccessGrant(id="user1", type="user", role=Role.OWNER), + AccessGrant(id="user2", type="user", role=Role.MEMBER), + AccessGrant(id="user3", type="user", role=Role.CONTRIBUTOR), + ] + + +@pytest.fixture +def sample_group_grants() -> List[AccessGrant]: + return [ + AccessGrant(id="group1", type="group", role=Role.OWNER), + AccessGrant(id="group2", type="group", role=Role.MEMBER), + ] + + +@pytest.fixture +def access_properties( + sample_user_grants, sample_group_grants +) -> KItemAccessProperties: + return KItemAccessProperties( + grants=sample_user_grants + sample_group_grants, + ) + + +def test_access_level_owner(): + """Test access_level property for OWNER role""" + grant = AccessGrant(id="u1", type="user", role=Role.OWNER) + expected = [ + OperationType.READ, + OperationType.UPDATE, + OperationType.DELETE, + OperationType.MANAGE, + ] + assert grant.access_level == expected + assert grant.role.value == Role.OWNER.value + + +def test_minimum_access_level(): + """Test min_access_level method""" + assert ( + RoleMapping.min_access_level(OperationType.READ) == Role.MEMBER.value + ) + assert ( + RoleMapping.min_access_level(OperationType.UPDATE) + == Role.CONTRIBUTOR.value + ) + assert ( + RoleMapping.min_access_level(OperationType.DELETE) == Role.OWNER.value + ) + assert ( + RoleMapping.min_access_level(OperationType.MANAGE) == Role.OWNER.value + ) + + +def test_maximum_access_level(): + """Test max_access_level method""" + assert RoleMapping.max_access_level(OperationType.READ) == Role.OWNER.value + assert ( + RoleMapping.max_access_level(OperationType.UPDATE) == Role.OWNER.value + ) + assert ( + RoleMapping.max_access_level(OperationType.DELETE) == Role.OWNER.value + ) + assert ( + RoleMapping.max_access_level(OperationType.MANAGE) == Role.OWNER.value + ) + + +def test_access_level_member(): + """Test access_level property for MEMBER role""" + grant = AccessGrant(id="u1", type="user", role=Role.MEMBER) + expected = [OperationType.READ] + assert grant.access_level == expected + assert grant.role.value == Role.MEMBER.value + + +def test_access_level_contributor(): + """Test access_level property for CONTRIBUTOR role""" + grant = AccessGrant(id="u1", type="user", role=Role.CONTRIBUTOR) + expected = [OperationType.READ, OperationType.UPDATE] + assert grant.access_level == expected + assert grant.role.value == Role.CONTRIBUTOR.value + + +def test_by_id_users(access_properties): + """Test by_id property returns correct lookup for user grants""" + result = access_properties.by_id + + assert "user1" in result + assert "user2" in result + assert "user3" in result + + assert result["user1"].role == Role.OWNER + assert result["user2"].role == Role.MEMBER + assert result["user3"].role == Role.CONTRIBUTOR + assert result["user1"].role.value == Role.OWNER.value + assert result["user2"].role.value == Role.MEMBER.value + assert result["user3"].role.value == Role.CONTRIBUTOR.value + + +def test_by_id_groups(access_properties): + """Test by_id property returns correct lookup for group grants""" + result = access_properties.by_id + + assert "group1" in result + assert "group2" in result + + assert result["group1"].role == Role.OWNER + assert result["group2"].role == Role.MEMBER + assert result["group1"].role.value == Role.OWNER.value + assert result["group2"].role.value == Role.MEMBER.value + + +def test_operation_by_user_principal(): + """Test operation_by_principal for user grants""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="user1", type="user", role=Role.OWNER), + AccessGrant(id="user2", type="user", role=Role.MEMBER), + AccessGrant(id="user3", type="user", role=Role.CONTRIBUTOR), + ] + ) + result = props.operation_by_principal + + assert set(result[OperationType.READ]) == {"user1", "user2", "user3"} + assert set(result[OperationType.UPDATE]) == {"user1", "user3"} + assert result[OperationType.DELETE] == ["user1"] + assert result[OperationType.MANAGE] == ["user1"] + + +def test_operation_by_group_principal(): + """Test operation_by_principal for group grants""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="group1", type="group", role=Role.OWNER), + AccessGrant(id="group2", type="group", role=Role.MEMBER), + ] + ) + result = props.operation_by_principal + + assert set(result[OperationType.READ]) == {"group1", "group2"} + assert result[OperationType.UPDATE] == ["group1"] + assert result[OperationType.DELETE] == ["group1"] + assert result[OperationType.MANAGE] == ["group1"] + + +def test_operation_by_principal_multiple_same_operation(): + """Test operation_by_principal with multiple principals having same operations""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="user1", type="user", role=Role.MEMBER), + AccessGrant(id="user2", type="user", role=Role.MEMBER), + AccessGrant(id="user3", type="user", role=Role.CONTRIBUTOR), + ] + ) + result = props.operation_by_principal + + assert set(result[OperationType.READ]) == {"user1", "user2", "user3"} + assert result[OperationType.UPDATE] == ["user3"] + + +def test_role_parsed_from_int(): + """Test that roles parsed from integer values work correctly""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="user1", type="user", role=1), + AccessGrant(id="user2", type="user", role=1), + AccessGrant(id="user3", type="user", role=2), + ] + ) + result = props.operation_by_principal + + assert set(result[OperationType.READ]) == {"user1", "user2", "user3"} + assert result[OperationType.UPDATE] == ["user3"] + assert props.by_id["user1"].role == Role.MEMBER + assert props.by_id["user1"].role.value == Role.MEMBER.value + + +def test_by_role_groups(): + """Test by_role with group grants""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="group1", type="group", role=Role.MEMBER), + AccessGrant(id="group2", type="group", role=Role.MEMBER), + AccessGrant(id="group3", type="group", role=Role.OWNER), + ] + ) + result = props.operation_by_principal + + assert set(result[OperationType.READ]) == {"group1", "group2", "group3"} + assert result[OperationType.MANAGE] == ["group3"] + assert props.by_role[Role.OWNER] == ["group3"] + assert props.by_role[Role.MEMBER] == ["group1", "group2"] + + +def test_model_creation_with_defaults(): + """Test model creation with default values""" + props = KItemAccessProperties() + + assert props.grants == [] + assert props.visibility == "private" + assert props.by_id == {} + assert props.by_role == {} + assert props.operation_by_principal == {} + + +def test_grant_creation(): + """Test AccessGrant creation and access_level""" + grant = AccessGrant(id="test_user", type="user", role=Role.CONTRIBUTOR) + + assert grant.id == "test_user" + assert grant.type == "user" + assert grant.role == Role.CONTRIBUTOR + assert grant.access_level == [OperationType.READ, OperationType.UPDATE] + + +def test_duplicate_user_grants_raises_error(): + """Test that duplicate (id, type) user grants raise ValueError""" + grants = [ + AccessGrant(id="user1", type="user", role=Role.OWNER), + AccessGrant(id="user2", type="user", role=Role.MEMBER), + AccessGrant( + id="user1", type="user", role=Role.CONTRIBUTOR + ), # Duplicate + ] + + with pytest.raises(ValidationError) as exc_info: + KItemAccessProperties(grants=grants) + + error_details = exc_info.value.errors() + assert len(error_details) == 1 + assert error_details[0]["type"] == "value_error" + assert "Duplicate grant found" in str(error_details[0]["ctx"]["error"]) + + +def test_duplicate_group_grants_raises_error(): + """Test that duplicate (id, type) group grants raise ValueError""" + grants = [ + AccessGrant(id="group1", type="group", role=Role.OWNER), + AccessGrant(id="group2", type="group", role=Role.MEMBER), + AccessGrant( + id="group1", type="group", role=Role.CONTRIBUTOR + ), # Duplicate + ] + + with pytest.raises(ValidationError) as exc_info: + KItemAccessProperties(grants=grants) + + error_details = exc_info.value.errors() + assert len(error_details) == 1 + assert error_details[0]["type"] == "value_error" + assert "Duplicate grant found" in str(error_details[0]["ctx"]["error"]) + + +def test_same_id_different_type_no_error(): + """Test that the same ID for user and group is not a duplicate""" + grants = [ + AccessGrant(id="shared-id", type="user", role=Role.OWNER), + AccessGrant(id="shared-id", type="group", role=Role.MEMBER), + ] + props = KItemAccessProperties(grants=grants) + assert len(props.grants) == 2 + + +def test_case_sensitive_ids(): + """Test that IDs are case sensitive (no duplicates if different case)""" + grants = [ + AccessGrant(id="User1", type="user", role=Role.OWNER), + AccessGrant(id="user1", type="user", role=Role.MEMBER), + AccessGrant(id="USER1", type="user", role=Role.CONTRIBUTOR), + ] + + props = KItemAccessProperties(grants=grants) + + assert len(props.grants) == 3 + assert props.by_id["User1"].role == Role.OWNER + assert props.by_id["user1"].role == Role.MEMBER + assert props.by_id["USER1"].role == Role.CONTRIBUTOR + assert props.by_role[Role.OWNER] == ["User1"] + assert props.by_role[Role.MEMBER] == ["user1"] + assert props.by_role[Role.CONTRIBUTOR] == ["USER1"] + + +def test_case_sensitive_ids_dict(): + """Test that IDs are case sensitive when passed as dicts""" + grants = [ + {"id": "User1", "type": "user", "role": 3}, + {"id": "user1", "type": "user", "role": 1}, + {"id": "USER1", "type": "user", "role": 2}, + ] + + props = KItemAccessProperties(grants=grants) + + assert len(props.grants) == 3 + assert props.by_id["User1"].role == Role.OWNER + assert props.by_id["user1"].role == Role.MEMBER + assert props.by_id["USER1"].role == Role.CONTRIBUTOR + assert props.by_role[Role.OWNER] == ["User1"] + assert props.by_role[Role.MEMBER] == ["user1"] + assert props.by_role[Role.CONTRIBUTOR] == ["USER1"] diff --git a/tests/test_access_extended.py b/tests/test_access_extended.py new file mode 100644 index 0000000..a1b5d38 --- /dev/null +++ b/tests/test_access_extended.py @@ -0,0 +1,220 @@ +"""Extended tests for access.py""" + +import pytest + +from dsms.knowledge.properties.access import ( + AccessGrant, + KItemAccessProperties, + OperationType, + Role, + RoleMapping, +) + +# --------------------------------------------------------------------------- +# Role hierarchy +# --------------------------------------------------------------------------- + + +def test_role_ordering(): + """Role integer values must be strictly ascending: MEMBER < CONTRIBUTOR < OWNER.""" + assert Role.MEMBER < Role.CONTRIBUTOR < Role.OWNER + + +def test_role_gte_comparison(): + """>= on Role values must work correctly for threshold checks.""" + assert Role.OWNER >= Role.CONTRIBUTOR + assert not (Role.MEMBER >= Role.CONTRIBUTOR) + + +# --------------------------------------------------------------------------- +# min_access_level / max_access_level return type +# --------------------------------------------------------------------------- + + +def test_min_access_level_returns_role_instance(): + """min_access_level must return a Role member, not a plain int.""" + result = RoleMapping.min_access_level(OperationType.READ) + assert isinstance(result, Role) + assert result is Role.MEMBER + + +def test_max_access_level_returns_role_instance(): + """max_access_level must return a Role member, not a plain int.""" + result = RoleMapping.max_access_level(OperationType.READ) + assert isinstance(result, Role) + assert result is Role.OWNER + + +@pytest.mark.parametrize( + "operation, expected_min", + [ + (OperationType.READ, Role.MEMBER), + (OperationType.UPDATE, Role.CONTRIBUTOR), + (OperationType.DELETE, Role.OWNER), + (OperationType.MANAGE, Role.OWNER), + ], +) +def test_min_access_level_correct_role(operation, expected_min): + assert RoleMapping.min_access_level(operation) is expected_min + + +@pytest.mark.parametrize( + "operation", + [ + OperationType.READ, + OperationType.UPDATE, + OperationType.DELETE, + OperationType.MANAGE, + ], +) +def test_max_access_level_is_owner(operation): + """OWNER always holds the maximum access level for every mapped operation.""" + assert RoleMapping.max_access_level(operation) is Role.OWNER + + +# --------------------------------------------------------------------------- +# Unmapped operations raise ValueError +# --------------------------------------------------------------------------- + + +def test_min_access_level_create_raises(): + """CREATE is not assigned to any role — min_access_level must raise ValueError.""" + with pytest.raises(ValueError, match="create"): + RoleMapping.min_access_level(OperationType.CREATE) + + +def test_max_access_level_create_raises(): + """CREATE is not assigned to any role — max_access_level must raise ValueError.""" + with pytest.raises(ValueError, match="create"): + RoleMapping.max_access_level(OperationType.CREATE) + + +def test_error_message_lists_valid_operations(): + """The ValueError message must tell the caller which operations are valid.""" + with pytest.raises(ValueError) as exc_info: + RoleMapping.min_access_level(OperationType.CREATE) + msg = str(exc_info.value) + for op in ( + OperationType.READ, + OperationType.UPDATE, + OperationType.DELETE, + OperationType.MANAGE, + ): + assert op.value in msg + + +# --------------------------------------------------------------------------- +# serialize_role_json — correct mode behaviour +# --------------------------------------------------------------------------- + + +def test_serialize_role_json_mode(): + """Python mode -> uppercase name string; JSON/wire mode -> lowercase name string.""" + grant = AccessGrant(id="u1", type="user", role=Role.OWNER) + + python_dump = grant.model_dump(mode="python") + assert python_dump["role"] == "OWNER" + assert isinstance(python_dump["role"], str) + + json_dump = grant.model_dump(mode="json") + assert json_dump["role"] == "owner" + assert isinstance(json_dump["role"], str) + + +# --------------------------------------------------------------------------- +# KItemAccessProperties wire format +# --------------------------------------------------------------------------- + + +def test_model_dump_json_produces_string_roles(): + """model_dump(mode='json') must produce lowercase string role values for the wire format.""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="u1", type="user", role=Role.OWNER), + AccessGrant(id="g1", type="group", role=Role.MEMBER), + ] + ) + payload = props.model_dump(mode="json") + + user_grant = next(g for g in payload["grants"] if g["id"] == "u1") + group_grant = next(g for g in payload["grants"] if g["id"] == "g1") + assert user_grant["role"] == "owner" + assert isinstance(user_grant["role"], str) + assert group_grant["role"] == "member" + assert isinstance(group_grant["role"], str) + + +def test_model_dump_python_produces_string_roles(): + """model_dump(mode='python') must produce string role names for display.""" + props = KItemAccessProperties( + grants=[AccessGrant(id="u1", type="user", role=Role.CONTRIBUTOR)] + ) + payload = props.model_dump(mode="python") + assert payload["grants"][0]["role"] == "CONTRIBUTOR" + + +def test_round_trip_from_backend_dict(): + """A payload as returned by the backend (string roles) must round-trip correctly.""" + backend_payload = { + "visibility": "internal", + "grants": [ + {"id": "alice", "type": "user", "role": "owner"}, + {"id": "bob", "type": "user", "role": "member"}, + {"id": "dsms:internal", "type": "group", "role": "member"}, + ], + } + + props = KItemAccessProperties(**backend_payload) + + assert props.by_id["alice"].role is Role.OWNER + assert props.by_id["bob"].role is Role.MEMBER + assert props.by_id["dsms:internal"].role is Role.MEMBER + assert props.visibility == "internal" + + re_serialised = props.model_dump(mode="json") + assert re_serialised["grants"][0] == { + "id": "alice", + "type": "user", + "role": "owner", + } + assert re_serialised["grants"][2] == { + "id": "dsms:internal", + "type": "group", + "role": "member", + } + + +# --------------------------------------------------------------------------- +# by_role property +# --------------------------------------------------------------------------- + + +def test_by_role(): + """by_role must group principal IDs by their Role.""" + props = KItemAccessProperties( + grants=[ + AccessGrant(id="alice", type="user", role=Role.OWNER), + AccessGrant(id="bob", type="user", role=Role.MEMBER), + AccessGrant(id="carol", type="user", role=Role.MEMBER), + ] + ) + by_role = props.by_role + assert by_role[Role.OWNER] == ["alice"] + assert set(by_role[Role.MEMBER]) == {"bob", "carol"} + assert Role.CONTRIBUTOR not in by_role + + +def test_by_role_empty(): + assert KItemAccessProperties().by_role == {} + + +# --------------------------------------------------------------------------- +# Default grants +# --------------------------------------------------------------------------- + + +def test_empty_grants_default(): + """Default grants must be an empty list.""" + props = KItemAccessProperties() + assert props.grants == [] + assert props.visibility == "private" diff --git a/tests/test_groups.py b/tests/test_groups.py new file mode 100644 index 0000000..07d5b20 --- /dev/null +++ b/tests/test_groups.py @@ -0,0 +1,894 @@ +"""Tests for groups models and DSMS user/group API.""" + +from urllib.parse import urljoin + +import pytest +import responses as responses_lib + +from dsms.knowledge.groups.models import ( + BaseGroup, + Group, + GroupList, + User, + UserList, +) + +# --------------------------------------------------------------------------- +# Group model +# --------------------------------------------------------------------------- + + +def test_group_basic(): + g = Group(id="grp-1", name="Engineering") + assert g.id == "grp-1" + assert g.name == "Engineering" + assert g.subgroups is None + + +def test_group_with_subgroups(): + child = Group(id="grp-child", name="Backend") + parent = Group(id="grp-parent", name="Engineering", subgroups=[child]) + assert len(parent.subgroups) == 1 + assert parent.subgroups[0].id == "grp-child" + + +def test_group_deeply_nested(): + leaf = Group(id="leaf", name="Leaf") + mid = Group(id="mid", name="Mid", subgroups=[leaf]) + root = Group(id="root", name="Root", subgroups=[mid]) + assert root.subgroups[0].subgroups[0].id == "leaf" + + +# --------------------------------------------------------------------------- +# GroupList +# --------------------------------------------------------------------------- + + +def test_grouplist_flat_no_subgroups(): + gl = GroupList([Group(id="a", name="A"), Group(id="b", name="B")]) + flat = gl.flat + assert {g.id for g in flat} == {"a", "b"} + + +def test_grouplist_flat_includes_subgroups(): + child = Group(id="child", name="Child") + parent = Group(id="parent", name="Parent", subgroups=[child]) + gl = GroupList([parent]) + flat = gl.flat + assert {g.id for g in flat} == {"parent", "child"} + + +def test_grouplist_flat_deep(): + leaf = Group(id="leaf", name="Leaf") + mid = Group(id="mid", name="Mid", subgroups=[leaf]) + root = Group(id="root", name="Root", subgroups=[mid]) + gl = GroupList([root]) + assert {g.id for g in gl.flat} == {"root", "mid", "leaf"} + + +def test_grouplist_flat_returns_basegroups(): + """flat must return BaseGroup instances (no subgroups field).""" + child = Group(id="child", name="Child") + parent = Group(id="parent", name="Parent", subgroups=[child]) + gl = GroupList([parent]) + for item in gl.flat: + assert isinstance(item, BaseGroup) + + +def test_grouplist_by_id(): + gl = GroupList([Group(id="a", name="Alpha"), Group(id="b", name="Beta")]) + by_id = gl.by_id + assert by_id["a"].name == "Alpha" + assert by_id["b"].name == "Beta" + + +def test_grouplist_by_id_includes_subgroups(): + child = Group(id="child", name="Child") + parent = Group(id="parent", name="Parent", subgroups=[child]) + gl = GroupList([parent]) + assert "child" in gl.by_id + assert "parent" in gl.by_id + + +def test_grouplist_by_name(): + gl = GroupList([Group(id="a", name="Alpha"), Group(id="b", name="Beta")]) + assert gl.by_name["Alpha"].id == "a" + assert gl.by_name["Beta"].id == "b" + + +# --------------------------------------------------------------------------- +# User model +# --------------------------------------------------------------------------- + + +def test_user_basic(): + u = User(id="u-1", username="alice") + assert u.id == "u-1" + assert u.username == "alice" + assert u.user_groups is None + + +def test_user_with_groups(): + g = BaseGroup(id="g-1", name="Engineering") + u = User(id="u-1", username="alice", user_groups=[g]) + assert len(u.user_groups) == 1 + assert u.user_groups[0].id == "g-1" + + +# --------------------------------------------------------------------------- +# UserList +# --------------------------------------------------------------------------- + + +def test_userlist_by_id(): + ul = UserList( + [User(id="u1", username="alice"), User(id="u2", username="bob")] + ) + assert ul.by_id["u1"].username == "alice" + assert ul.by_id["u2"].username == "bob" + + +def test_userlist_by_username(): + ul = UserList( + [User(id="u1", username="alice"), User(id="u2", username="bob")] + ) + assert ul.by_username["alice"].id == "u1" + + +def test_userlist_by_name_is_alias_for_by_username(): + ul = UserList([User(id="u1", username="alice")]) + assert ul.by_name == ul.by_username + assert ul.by_name["alice"].id == "u1" + + +def test_userlist_getitem_by_id(): + ul = UserList([User(id="u1", username="alice")]) + assert ul["u1"].username == "alice" + + +def test_userlist_getitem_missing_raises(): + ul = UserList([User(id="u1", username="alice")]) + with pytest.raises(KeyError): + _ = ul["nonexistent"] + + +# --------------------------------------------------------------------------- +# DSMS.user_groups, DSMS.users, DSMS.get_user — caching and HTTP behaviour +# --------------------------------------------------------------------------- + +MOCK_GROUPS = [ + {"id": "grp-1", "name": "Engineering", "subgroups": []}, + { + "id": "grp-2", + "name": "Research", + "subgroups": [{"id": "grp-3", "name": "ML", "subgroups": []}], + }, +] + +MOCK_USERS = [ + {"id": "u-1", "username": "alice", "user_groups": []}, + {"id": "u-2", "username": "bob", "user_groups": []}, +] + +MOCK_SINGLE_USER = {"id": "u-1", "username": "alice", "user_groups": []} + + +@responses_lib.activate +def test_user_groups_returns_grouplist(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups"), + json=MOCK_GROUPS, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + result = dsms.user_groups + assert isinstance(result, GroupList) + assert len(result) == 2 + assert result.by_id["grp-1"].name == "Engineering" + + +@responses_lib.activate +def test_user_groups_is_cached(custom_address): + """Second access to user_groups must not make a second HTTP request.""" + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups"), + json=MOCK_GROUPS, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + _ = dsms.user_groups + _ = dsms.user_groups + + group_calls = [ + c for c in responses_lib.calls if "api/users/groups" in c.request.url + ] + assert len(group_calls) == 1 + + +@responses_lib.activate +def test_refresh_user_groups_re_fetches(custom_address): + """refresh_user_groups() must make a new HTTP request and update the cache.""" + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups"), + json=MOCK_GROUPS, + status=200, + ) + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups"), + json=[{"id": "grp-new", "name": "New Group", "subgroups": []}], + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + _ = dsms.user_groups + dsms.refresh_user_groups() + + assert dsms.user_groups.by_id["grp-new"].name == "New Group" + group_calls = [ + c for c in responses_lib.calls if "api/users/groups" in c.request.url + ] + assert len(group_calls) == 2 + + +@responses_lib.activate +def test_users_is_cached(custom_address): + """Second access to users must not make a second HTTP request.""" + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/"), + json=MOCK_USERS, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + _ = dsms.users + _ = dsms.users + + user_calls = [ + c for c in responses_lib.calls if "api/users/" in c.request.url + ] + assert len(user_calls) == 1 + + +@responses_lib.activate +def test_refresh_users_re_fetches(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/"), + json=MOCK_USERS, + status=200, + ) + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/"), + json=[{"id": "u-3", "username": "carol", "user_groups": []}], + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + _ = dsms.users + dsms.refresh_users() + + assert dsms.users["u-3"].username == "carol" + + +@responses_lib.activate +def test_get_user_returns_user_object(custom_address): + """DSMS.get_user(id) must return a typed User, not a raw dict.""" + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/u-1"), + json=MOCK_SINGLE_USER, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + user = dsms.get_user("u-1") + + assert isinstance(user, User) + assert user.id == "u-1" + assert user.username == "alice" + + +@responses_lib.activate +def test_get_user_raises_on_missing(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/nonexistent"), + json={"detail": "Not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="nonexistent"): + dsms.get_user("nonexistent") + + +# --------------------------------------------------------------------------- +# Group CRUD — get_group_members +# --------------------------------------------------------------------------- + +MOCK_MEMBERS = [ + { + "id": "u-1", + "username": "alice", + "firstName": "Alice", + "lastName": "A", + "email": "alice@example.com", + }, + { + "id": "u-2", + "username": "bob", + "firstName": "Bob", + "lastName": "B", + "email": "bob@example.com", + }, +] + + +@responses_lib.activate +def test_get_group_members_returns_user_list(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups/grp-1/members"), + json=MOCK_MEMBERS, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + members = dsms.get_group_members("grp-1") + assert len(members) == 2 + assert all(isinstance(m, User) for m in members) + assert members[0].username == "alice" + assert members[1].username == "bob" + + +@responses_lib.activate +def test_get_group_members_raises_on_error(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups/bad-id/members"), + json={"detail": "Not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ConnectionError, match="bad-id"): + dsms.get_group_members("bad-id") + + +# --------------------------------------------------------------------------- +# Group CRUD — create_group +# --------------------------------------------------------------------------- + +MOCK_NEW_GROUP = {"id": "grp-new", "name": "New Group"} +MOCK_NEW_SUBGROUP = {"id": "grp-sub", "name": "Sub Group"} + + +@responses_lib.activate +def test_create_group_top_level(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin(custom_address, "api/users/groups"), + json=MOCK_NEW_GROUP, + status=201, + ) + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups"), + json=[MOCK_NEW_GROUP], + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + group = dsms.create_group("New Group") + assert isinstance(group, Group) + assert group.id == "grp-new" + assert group.name == "New Group" + # cache must be invalidated + assert dsms._user_groups is None + + +@responses_lib.activate +def test_create_group_as_subgroup(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin(custom_address, "api/users/groups/grp-1/subgroups"), + json=MOCK_NEW_SUBGROUP, + status=201, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + group = dsms.create_group("Sub Group", parent_id="grp-1") + assert isinstance(group, Group) + assert group.id == "grp-sub" + + call = responses_lib.calls[0] + assert "grp-1/subgroups" in call.request.url + + +@responses_lib.activate +def test_create_group_raises_on_error(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin(custom_address, "api/users/groups"), + json={"detail": "Bad request"}, + status=400, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="Failed to create group"): + dsms.create_group("Bad") + + +# --------------------------------------------------------------------------- +# Group CRUD — update_group +# --------------------------------------------------------------------------- + + +@responses_lib.activate +def test_update_group_name(custom_address): + responses_lib.add( + responses_lib.PUT, + urljoin(custom_address, "api/users/groups/grp-1"), + json={"id": "grp-1", "name": "Renamed"}, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + updated = dsms.update_group("grp-1", name="Renamed") + assert isinstance(updated, Group) + assert updated.name == "Renamed" + assert dsms._user_groups is None + + +@responses_lib.activate +def test_update_group_raises_when_nothing_provided(custom_address): + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="At least one"): + dsms.update_group("grp-1") + + +# --------------------------------------------------------------------------- +# Group CRUD — delete_group +# --------------------------------------------------------------------------- + + +@responses_lib.activate +def test_delete_group(custom_address): + responses_lib.add( + responses_lib.DELETE, + urljoin(custom_address, "api/users/groups/grp-1"), + status=204, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + dsms.delete_group("grp-1") + assert dsms._user_groups is None + assert len(responses_lib.calls) == 1 + assert "groups/grp-1" in responses_lib.calls[0].request.url + + +@responses_lib.activate +def test_delete_group_raises_on_error(custom_address): + responses_lib.add( + responses_lib.DELETE, + urljoin(custom_address, "api/users/groups/grp-missing"), + json={"detail": "Not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="Failed to delete"): + dsms.delete_group("grp-missing") + + +# --------------------------------------------------------------------------- +# Group CRUD — add_group_member / remove_group_member +# --------------------------------------------------------------------------- + + +@responses_lib.activate +def test_add_group_member(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin(custom_address, "api/users/groups/grp-1/members"), + status=204, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + dsms.add_group_member("grp-1", "u-1") + call = responses_lib.calls[0] + assert "groups/grp-1/members" in call.request.url + import json as _json + + body = _json.loads(call.request.body) + assert body["user_id"] == "u-1" + + +@responses_lib.activate +def test_add_group_member_raises_on_error(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin(custom_address, "api/users/groups/grp-1/members"), + json={"detail": "User not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="Failed to add user"): + dsms.add_group_member("grp-1", "bad-user") + + +@responses_lib.activate +def test_remove_group_member(custom_address): + responses_lib.add( + responses_lib.DELETE, + urljoin(custom_address, "api/users/groups/grp-1/members/u-1"), + status=204, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + dsms.remove_group_member("grp-1", "u-1") + call = responses_lib.calls[0] + assert "groups/grp-1/members/u-1" in call.request.url + + +@responses_lib.activate +def test_remove_group_member_raises_on_error(custom_address): + responses_lib.add( + responses_lib.DELETE, + urljoin(custom_address, "api/users/groups/grp-1/members/u-bad"), + json={"detail": "User not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="Failed to remove user"): + dsms.remove_group_member("grp-1", "u-bad") + + +# --------------------------------------------------------------------------- +# Group-in-group: get_group_subgroups / add_group_to_group / remove_group_from_group +# --------------------------------------------------------------------------- + +MOCK_SUBGROUPS = [ + {"id": "grp-child-1", "name": "Child A"}, + {"id": "grp-child-2", "name": "Child B"}, +] + + +@responses_lib.activate +def test_get_group_subgroups_returns_grouplist(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups/grp-1/subgroups"), + json=MOCK_SUBGROUPS, + status=200, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + subs = dsms.get_group_subgroups("grp-1") + assert isinstance(subs, GroupList) + assert len(subs) == 2 + assert subs.by_id["grp-child-1"].name == "Child A" + assert subs.by_id["grp-child-2"].name == "Child B" + + +@responses_lib.activate +def test_get_group_subgroups_raises_on_error(custom_address): + responses_lib.add( + responses_lib.GET, + urljoin(custom_address, "api/users/groups/bad-id/subgroups"), + json={"detail": "Not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ConnectionError, match="bad-id"): + dsms.get_group_subgroups("bad-id") + + +@responses_lib.activate +def test_add_group_to_group(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin( + custom_address, "api/users/groups/grp-parent/subgroups/grp-child" + ), + status=204, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + dsms.add_group_to_group("grp-parent", "grp-child") + + assert len(responses_lib.calls) == 1 + assert ( + "groups/grp-parent/subgroups/grp-child" + in responses_lib.calls[0].request.url + ) + # cache must be invalidated + assert dsms._user_groups is None + + +@responses_lib.activate +def test_add_group_to_group_raises_on_error(custom_address): + responses_lib.add( + responses_lib.POST, + urljoin( + custom_address, "api/users/groups/grp-parent/subgroups/bad-child" + ), + json={"detail": "Group not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="bad-child"): + dsms.add_group_to_group("grp-parent", "bad-child") + + +@responses_lib.activate +def test_remove_group_from_group(custom_address): + responses_lib.add( + responses_lib.DELETE, + urljoin( + custom_address, "api/users/groups/grp-parent/subgroups/grp-child" + ), + status=204, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + dsms.remove_group_from_group("grp-parent", "grp-child") + + assert len(responses_lib.calls) == 1 + assert ( + "groups/grp-parent/subgroups/grp-child" + in responses_lib.calls[0].request.url + ) + assert dsms._user_groups is None + + +@responses_lib.activate +def test_remove_group_from_group_raises_on_error(custom_address): + responses_lib.add( + responses_lib.DELETE, + urljoin( + custom_address, "api/users/groups/grp-parent/subgroups/bad-child" + ), + json={"detail": "Group not found"}, + status=404, + ) + + with pytest.warns(UserWarning): + from dsms.core.dsms import DSMS + + dsms = DSMS( + host_url=custom_address, + ping_backend=False, + auto_fetch_ktypes=False, + ) + + with pytest.raises(ValueError, match="bad-child"): + dsms.remove_group_from_group("grp-parent", "bad-child") + + +# --------------------------------------------------------------------------- +# GroupListBase removed — flat returns a plain list of BaseGroup +# --------------------------------------------------------------------------- + + +def test_grouplist_flat_is_plain_list(): + """After removing GroupListBase, .flat must return a plain list.""" + gl = GroupList( + [Group(id="a", name="A", subgroups=[Group(id="b", name="B")])] + ) + result = gl.flat + assert isinstance(result, list) + assert not type(result).__name__ == "GroupListBase" + assert all(isinstance(g, BaseGroup) for g in result) diff --git a/tests/test_kitem.py b/tests/test_kitem.py index 0f09770..3f41a9a 100644 --- a/tests/test_kitem.py +++ b/tests/test_kitem.py @@ -1,4 +1,5 @@ """Pytest for basic KItem connection properties""" + import pytest import responses diff --git a/tests/test_utils.py b/tests/test_utils.py index e988333..9981339 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -51,7 +51,6 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): name="bar123", ) - user_group = {"name": "private", "group_id": "private_123"} app = {"executable": "foo.exe", "title": "foo"} kitem_old = { @@ -87,7 +86,6 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): }, }, ], - "user_groups": [user_group], "apps": [ { "id": get_mock_kitem_ids[0], @@ -115,7 +113,6 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): } ], linked_kitems=[linked_kitem3], - user_groups=[user_group], apps=[app], ) @@ -136,7 +133,6 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): "namespace": "example", } ], - "user_groups_to_add": [], "kitem_apps_to_update": [ { "executable": "foo.exe", @@ -162,7 +158,6 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): "namespace": "example", } ], - "user_groups_to_remove": [], "kitem_apps_to_remove": [ { "executable": "bar.exe", @@ -183,6 +178,102 @@ def test_kitem_diffs(get_mock_kitem_ids, custom_address): assert len(expected) == 0 +@responses.activate +def test_kitem_diffs_access_properties(get_mock_kitem_ids, custom_address): + """visibility must be promoted to a top-level key; must not appear inside access_properties.""" + import pytest + + from dsms.core.dsms import DSMS + from dsms.knowledge.kitem import KItem + from dsms.knowledge.properties.access import KItemAccessProperties, Role + from dsms.knowledge.utils import _get_kitems_diffs + + with pytest.warns(UserWarning, match="No authentication details"): + dsms = DSMS(host_url=custom_address) + + user_id = "abc-123" + + kitem_old = { + "id": get_mock_kitem_ids[0], + "name": "foo", + "ktype_id": dsms.ktypes.Organization.value, + "annotations": [], + "linked_kitems": [], + "apps": [], + "access_properties": { + "visibility": "private", + "user_access": [{"user_id": user_id, "role": "owner"}], + "group_access": [], + }, + } + + kitem_new = KItem( + id=get_mock_kitem_ids[0], + name="foo-item", + slug="foo-item", + ktype_id=dsms.ktypes.Organization, + access_properties=KItemAccessProperties( + visibility="internal", + user_access=[{"user_id": user_id, "role": Role.OWNER}], + ), + ) + + diffs = _get_kitems_diffs(kitem_old, kitem_new) + + assert diffs["visibility"] == "internal" + assert "visibility" not in diffs["access_properties"] + + +@responses.activate +def test_kitem_diffs_visibility_unchanged(get_mock_kitem_ids, custom_address): + """When visibility hasn't changed it should not appear in the diff.""" + import pytest + + from dsms.core.dsms import DSMS + from dsms.knowledge.kitem import KItem + from dsms.knowledge.properties.access import KItemAccessProperties, Role + from dsms.knowledge.utils import _get_kitems_diffs + + with pytest.warns(UserWarning, match="No authentication details"): + dsms = DSMS(host_url=custom_address) + + user_id = "abc-123" + other_user_id = "def-456" + + kitem_old = { + "id": get_mock_kitem_ids[0], + "name": "foo", + "ktype_id": dsms.ktypes.Organization.value, + "annotations": [], + "linked_kitems": [], + "apps": [], + "access_properties": { + "visibility": "internal", + "user_access": [{"user_id": user_id, "role": "owner"}], + "group_access": [], + }, + } + + kitem_new = KItem( + id=get_mock_kitem_ids[0], + name="foo-item", + slug="foo-item", + ktype_id=dsms.ktypes.Organization, + access_properties=KItemAccessProperties( + visibility="internal", + user_access=[ + {"user_id": user_id, "role": Role.OWNER}, + {"user_id": other_user_id, "role": Role.MEMBER}, + ], + ), + ) + + diffs = _get_kitems_diffs(kitem_old, kitem_new) + + assert "visibility" not in diffs + assert "visibility" not in diffs["access_properties"] + + @responses.activate def test_unit_conversion(custom_address): """Test unit conversion test"""