Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for adding and updating JSON-LD directly (partials for WMS) #149

Merged
merged 14 commits into from
Mar 31, 2023
57 changes: 55 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,60 @@ Note that entities can have multiple types, e.g.:
"@type" = ["File", "SoftwareSourceCode"]
```

#### Modifying the crate from JSON-LD dictionaries

The `add_jsonld` method allows to add a contextual entity directly from a
JSON-LD dictionary containing at least the `@id` and `@type` keys:

```python
crate.add_jsonld({
"@id": "https://orcid.org/0000-0000-0000-0000",
"@type": "Person",
"name": "Alice Doe"
})
```

Existing entities can be updated from JSON-LD dictionaries via `update_jsonld`:

```python
crate.update_jsonld({
"@id": "https://orcid.org/0000-0000-0000-0000",
"name": "Alice K. Doe"
})
```

There is also an `add_or_update_jsonld` method that adds the entity if it's
not already in the crate and updates it if it already exists (note that, when
updating, the `@type` key is ignored). This allows to "patch" an RO-Crate from
a JSON-LD file. For instance, suppose you have the following `patch.json` file:

```json
{
"@graph": [
{
"@id": "./",
"author": {"@id": "https://orcid.org/0000-0000-0000-0001"}
},
{
"@id": "https://orcid.org/0000-0000-0000-0001",
"@type": "Person",
"name": "Bob Doe"
}
]
}
```

Then the following sets Bob as the author of the crate according to the above
file:

```python
crate = ROCrate("temp-crate")
with open("patch.json") as f:
json_data = json.load(f)
for d in json_data.get("@graph", []):
crate.add_or_update_jsonld(d)
```

### Consuming an RO-Crate

An existing RO-Crate package can be loaded from a directory or zip file:
Expand Down Expand Up @@ -278,7 +332,7 @@ Note that data entities (e.g., workflows) must already be present in the directo
cd test/test-data/ro-crate-galaxy-sortchangecase
```

This directory is already an ro-crate. Delete the metadata file to get a plain directory tree:
This directory is already an RO-Crate. Delete the metadata file to get a plain directory tree:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only place ro-crate was used, the rest appeared to use RO-Crate, so I changed the text here to match others.


```bash
rm ro-crate-metadata.json
Expand Down Expand Up @@ -315,7 +369,6 @@ rocrate add test-instance test1 http://example.com -r jobs -i test1_1
rocrate add test-definition test1 test/test1/sort-and-change-case-test.yml -e planemo -v '>=0.70'
```


## License

* Copyright 2019-2022 The University of Manchester, UK
Expand Down
4 changes: 2 additions & 2 deletions rocrate/model/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self, crate, identifier=None, properties=None):
if identifier:
self.id = self.format_id(identifier)
else:
self.id = "#%s" % uuid.uuid4()
self.id = f"#{uuid.uuid4()}"
if properties:
empty = self._empty()
empty.update(properties)
Expand All @@ -46,7 +46,7 @@ def format_id(self, identifier):
return str(identifier)

def __repr__(self):
return "<%s %s>" % (self.id, self.type)
return f"<{self.id} {self.type}>"

def properties(self):
return self._jsonld
Expand Down
70 changes: 70 additions & 0 deletions rocrate/rocrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,76 @@ def add_test_definition(
self.metadata.extra_terms.update(TESTING_EXTRA_TERMS)
return definition

def add_jsonld(self, jsonld):
"""Add a JSON-LD dictionary as a contextual entity to the RO-Crate.

The `@id` and `@type` keys must be present in the JSON-LD dictionary.

Args:
jsonld: A JSON-LD dictionary containing at least `@id` and `@type`.
Return:
The entity added to the RO-Crate.
Raises:
ValueError: if the jsonld object is empty or None or if the
entity already exists (found via @id).
"""
required_keys = {"@id", "@type"}
if not jsonld or not required_keys.issubset(jsonld):
raise ValueError("you must provide a non-empty JSON-LD dictionary with @id and @type set")
entity_id = jsonld.pop("@id")
if self.get(entity_id):
raise ValueError(f"entity {entity_id} already exists in the RO-Crate")
return self.add(ContextEntity(
self,
entity_id,
properties=jsonld
))

def update_jsonld(self, jsonld):
"""Update an entity in the RO-Crate from a JSON-LD dictionary.

An `@id` must be present in the JSON-LD dictionary. Any other keys
in the JSON-LD dictionary that start with `@` will be removed.

Args:
jsonld: A JSON-LD dictionary.
Return:
The updated entity.
Raises:
ValueError: if the jsonld object is empty or None, if @id was not
provided, or if the entity was not found.
"""
if not jsonld or "@id" not in jsonld:
raise ValueError("you must provide a non-empty JSON-LD dictionary")
entity_id = jsonld.pop("@id")
entity: Entity = self.get(entity_id)
if not entity:
raise ValueError(f"entity {entity_id} does not exist in the RO-Crate")
jsonld = {k: v for k, v in jsonld.items() if not k.startswith('@')}
entity._jsonld.update(jsonld)
return entity

def add_or_update_jsonld(self, jsonld):
"""Add or update an entity from a JSON-LD dictionary.

An `@id` must be present in the JSON-LD dictionary.

Args:
jsonld: A JSON-LD dictionary.
Return:
The added or updated entity.
Raises:
ValueError: if the jsonld object is empty or None or if @id was not
provided.
"""
if not jsonld or "@id" not in jsonld:
raise ValueError("you must provide a non-empty JSON-LD dictionary")
entity_id = jsonld.get("@id")
entity: Entity = self.get(entity_id)
if not entity:
return self.add_jsonld(jsonld)
return self.update_jsonld(jsonld)

def __validate_suite(self, suite):
if isinstance(suite, TestSuite):
assert suite.crate is self
Expand Down
Loading