|
| 1 | +<!-- |
| 2 | +SPDX-FileCopyrightText: 2026 German Aerospace Center (DLR), Forschungszentrum Jülich, Helmholtz-Zentrum Dresden-Rossendorf |
| 3 | +
|
| 4 | +SPDX-License-Identifier: CC-BY-SA-4.0 |
| 5 | +--> |
| 6 | + |
| 7 | +# Capabilities of the internal data model |
| 8 | + |
| 9 | +* Status: proposed |
| 10 | +* Date: 2026-03-17 |
| 11 | + |
| 12 | +## Context and Problem Statement |
| 13 | + |
| 14 | +As decided in [ADR 2](./0002-use-a-common-data-model) the metadata that is created and manipulated by HERMES is stored as JSON-LD. |
| 15 | +But the critical questions how it is stored and how it can be accessed are not yet discussed. |
| 16 | +There are a few requirements though: |
| 17 | +* The data should probably be stored in some form of expanded JSON-LD. |
| 18 | +* Read and write access should be possible with non expanded JSON-LD (the values then have to be expanded). |
| 19 | +* The objects should be as user friendly as possible (supply many different ways to interact with the data). |
| 20 | + |
| 21 | +## Considered Options |
| 22 | + |
| 23 | +* Providing our own JSON-LD wrapper classes |
| 24 | + |
| 25 | +## Decision Outcome |
| 26 | + |
| 27 | +Chosen option: "", because comes out best. |
| 28 | + |
| 29 | +## Pros and Cons of the Options |
| 30 | + |
| 31 | +### Providing our own JSON-LD wrapper classes |
| 32 | + |
| 33 | +This includes a base class (supplying basic functions like expansion and compaction named `ld_container`), a class representing dictionaries (named `ld_dict`) and one for list-like objects (like @list and @set named `ld_list`). Additionally a wrapper class (named SoftwareMetdata) for complete sets of metadata of SoftwareSourceCode and SoftwareApplication (schema.org types) is supplied which is an `ld_dict` that has a standard context and supplies a function to load from the HERMES cache. |
| 34 | +Furthermore for processing of JSON-LD the `JsonLdProcessor` from `jsonld` from the [pyld](https://pypi.org/project/PyLD/) package is used. |
| 35 | + |
| 36 | +The following features will be supported: |
| 37 | +```python |
| 38 | +from hermes.model import SoftwareMetadata |
| 39 | + |
| 40 | +# initializing SoftwareMetadata objects |
| 41 | +SoftwareMetadata() # contains no data and only standard context |
| 42 | +SoftwareMetadata(extra_vocabs=ctx) # contains no data but extra context (ctx is a dict mapping shortend prefixes to full iri's) |
| 43 | +SoftwareMetadata(data) # data can be any valid JSON-LD dictionary (where dicts and lists can be replaced by ld_dicts and ld_lists) |
| 44 | +SoftwareMetadata(data, ctx) # contains the given data and context additionally to the standard context |
| 45 | + |
| 46 | +metadata = SoftwareMetadata(data) |
| 47 | +# getting values from ld_dicts (here metadata) |
| 48 | +# key may be compacted or expanded, returned is always an ld_list |
| 49 | +metadata[key] # KeyError if no value in metadata for that key |
| 50 | +metadata.get(key, default_value) # if default_value is given, it is returned when no entry for key is in metadata |
| 51 | +metadata.set_default(key, default_value) # inserts the default_value before returning metadata[key] if no entry for key is in metadata |
| 52 | + # default value may only be a value that can be inserted as a value of key |
| 53 | +# iterating over ld_dicts |
| 54 | +for key, value in metadata.items(): # iterating over all key, value pairs, value is metadata[key] |
| 55 | + # do stuff |
| 56 | +for key in metadata.keys(): # iterating over all expanded keys |
| 57 | + # do stuff |
| 58 | +for compact_key in metadata.compact_keys(): # iterating over all compacted keys |
| 59 | + # do stuff |
| 60 | +# setting values in ld_dicts |
| 61 | +# key may be compacted or expanded, value may be any valid JSON-LD value (where dicts and lists can be replaced by ld_dicts and ld_lists) |
| 62 | +metadata[key] = value |
| 63 | +metadata.set_default(key, value) # sets metadata[key] to value if metadata had no entry for key before |
| 64 | +metadata.update(values) # values is a dict mapping keys to values (each with the same constrictions as key and value) |
| 65 | +metadata.emplace(key) # equivalent to metadata[key] = [] if key not in metadata |
| 66 | +# misc functions for ld_dicts |
| 67 | +bool(metadata) # False if and only if metadata == {} |
| 68 | +metadata == value # ld_dicts are comparable to dicts and ld_dicts |
| 69 | +metadata != value |
| 70 | +key in metadata # checks if there is an entry for that key |
| 71 | +metadata.to_python() # return a pythonized version of the contents (compacted version where all ld_dicts are dicts and ld_lists lists) |
| 72 | +del metadata[key] # removes the entry of key |
| 73 | +metadata.ref # returns {"@id": metadata["@id"]} |
| 74 | + |
| 75 | +metadata_list = SoftwareMetadata(data)[key] |
| 76 | +# getting values from ld_lists (here metadata_list) |
| 77 | +# returned single values can be ld_lists, ld_dicts, ints, floats, bools, strings, dates, datetimes, times |
| 78 | +metadata_list[index] # index may be int or slice (when slice a list of single values is returned) |
| 79 | +# iterating over ld_lists |
| 80 | +for item in metadata_list: # iterating over all items |
| 81 | + # do stuff |
| 82 | +for index in range(len(metadata_list)): # iterate over all indices |
| 83 | + # do stuff |
| 84 | +# setting values in ld_lists |
| 85 | +# value may be any valid JSON-LD value (where dicts and lists can be replaced by ld_dicts and ld_lists) |
| 86 | +metadata_list[index] = value # index is int |
| 87 | +metadata_list[index] = values # index is slice and values is some iterable of values that share the constrictions of value |
| 88 | +metadata_list.append(value) |
| 89 | +metadata_list.extend(values) # values is some iterable of values that share the constrictions of value |
| 90 | +# misc functions for ld_lists |
| 91 | +len(metadata_list) # gives the number of elements |
| 92 | +metadata_list == value # ld_lists are comparable to ld_lists and lists |
| 93 | +metadata_list != value |
| 94 | +metadata_list.to_python() # return a pythonized version of the contents (compacted version where all ld_dicts are dicts and ld_lists lists) |
| 95 | +del metadata_list[index] # removes the entry/ entries at index, where index is int or slice |
| 96 | +value in metadata_list # checks if a value is in the list |
| 97 | + |
| 98 | +metadata = SoftwareMetadata(data) |
| 99 | +# additional valuable information |
| 100 | +metadata_list_copy = metadata_list = metadata[key] # assume metadata has an entry for key |
| 101 | +metadata_list.append(value) # any operation here will have an effect on metadata and metadata_list_copy (this works for every nesting depth) |
| 102 | +``` |
| 103 | + |
| 104 | +* Good, because it keeps the expanded JSON-LD data safe from getting invalidated by accidental wrong manipulation |
| 105 | +* Good, because it offers much flexibility and easy access for the user/ plugin developers |
| 106 | +* Good, because it could be extended to record provenance information on every manipulation |
| 107 | +* Bad, because hard to maintain |
0 commit comments