Skip to content

Commit

Permalink
MetaData rework (#281)
Browse files Browse the repository at this point in the history
* Add new feature to set metadata

* Add metadataitem

* Update metadata

* Fix pylint

* Fix spelling mistake

* Finish tests

* Fix issue with setting values

* fix regex for py3.12

* Update ibridges/meta.py

Co-authored-by: chStaiger <[email protected]>

* Add more information about metadata

* Improve error messaging

* Fix test name

* Fix test and error message

---------

Co-authored-by: chstaiger <[email protected]>
Co-authored-by: chStaiger <[email protected]>
  • Loading branch information
3 people authored Nov 14, 2024
1 parent ad70ef4 commit 1a8a681
Show file tree
Hide file tree
Showing 3 changed files with 418 additions and 50 deletions.
119 changes: 118 additions & 1 deletion docker/irods_client/tests/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pytest import mark

from ibridges.data_operations import Operations
from ibridges.meta import MetaData
from ibridges.meta import MetaData, MetaDataItem
from ibridges.path import IrodsPath


Expand Down Expand Up @@ -66,6 +66,7 @@ def test_meta(item_name, request):
assert "x" in meta
assert ("y", "z") not in meta
assert ("y", "x") in meta
meta.clear()

@mark.parametrize("item_name", ["collection", "dataobject"])
def test_metadata_todict(item_name, request):
Expand Down Expand Up @@ -108,3 +109,119 @@ def test_metadata_export(item_name, request, session, tmpdir):
with open(tmp_file, "r", encoding="utf-8"):
new_meta_dict = json.load(tmp_file)
assert isinstance(new_meta_dict, dict)

@mark.parametrize("item_name", ["collection", "dataobject"])
def test_metadata_getitem(item_name, request):
item = request.getfixturevalue(item_name)
meta = MetaData(item)
meta.clear()

assert len(meta) == 0
meta.add("some_key", "some_value", "some_units")
assert isinstance(meta["some_key"], MetaDataItem)
meta.add("some_key", "some_value", None)
meta.add("some_key", "other_value", "some_units")
meta.add("other_key", "third_value", "other_units")
with pytest.raises(ValueError):
meta["some_key"]
with pytest.raises(ValueError):
meta["some_key", "some_value"]
assert isinstance(meta["some_key", "some_value", "some_units"], MetaDataItem)
assert tuple(meta["other_key"]) == ("other_key", "third_value", "other_units")
with pytest.raises(KeyError):
meta["unknown"]
with pytest.raises(KeyError):
meta["some_key", "unknown"]
with pytest.raises(KeyError):
meta["some_key", "some_value", "unknown"]
meta.clear()


@mark.parametrize("item_name", ["collection", "dataobject"])
def test_metadata_rename(item_name, request, session):
item = request.getfixturevalue(item_name)
meta = MetaData(item)
meta.clear()


meta.add("some_key", "some_value", "some_units")
meta["some_key"].key = "new_key"
assert ("new_key", "some_value", "some_units") in meta
assert len(meta) == 1

meta["new_key"].value = "new_value"
assert ("new_key", "new_value", "some_units") in meta
assert len(meta) == 1

meta["new_key"].units = "new_units"
assert ("new_key", "new_value", "new_units") in meta
assert len(meta) == 1

meta.add("new_key", "new_value", "other_units")
with pytest.raises(ValueError):
meta["new_key", "new_value", "other_units"].units = "new_units"
assert len(meta) == 2
meta["new_key", "new_value", "other_units"].remove()

meta.add("new_key", "other_value", "new_units")
with pytest.raises(ValueError):
meta["new_key", "other_value", "new_units"].value = "new_value"
assert len(meta) == 2
meta["new_key", "other_value", "new_units"].remove()

meta.add("other_key", "new_value", "new_units")
with pytest.raises(ValueError):
meta["other_key", "new_value", "new_units"].key = "new_key"
assert len(meta) == 2

with pytest.raises(ValueError):
meta["other_key"].key = "org_something"
assert len(meta) == 2
assert "other_key" in meta

meta.clear()


@mark.parametrize("item_name", ["collection", "dataobject"])
def test_metadata_findall(item_name, request, session):
item = request.getfixturevalue(item_name)
meta = MetaData(item)
meta.clear()


meta.add("some_key", "some_value", "some_units")
meta.add("some_key", "some_value", None)
meta.add("some_key", "other_value", "some_units")
meta.add("other_key", "third_value", "other_units")

assert len(meta.find_all()) == 4
assert len(meta.find_all(key="some_key")) == 3
assert isinstance(meta.find_all(key="some_key")[0], MetaDataItem)
assert len(meta.find_all(key="?")) == 0
assert len(meta.find_all(value="some_value")) == 2
assert len(meta.find_all(units="some_units")) == 2


@mark.parametrize("item_name", ["collection", "dataobject"])
def test_metadata_errors(item_name, request, session):
item = request.getfixturevalue(item_name)
meta = MetaData(item)
meta.clear()

with pytest.raises(ValueError):
meta.add("", "some_value")
with pytest.raises(TypeError):
meta.add(None, "some_value")
with pytest.raises(TypeError):
meta.add(10, "some_value")

with pytest.raises(ValueError):
meta.add("key", "")
with pytest.raises(TypeError):
meta.add("key", None)
with pytest.raises(TypeError):
meta.add("key", 10)

with pytest.raises(TypeError):
meta.add("key", "value", 10)

66 changes: 62 additions & 4 deletions docs/source/metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Metadata
iRODS offers metadata as key, value, units triplets. The type of the keys, values and units is always a string.
Below we show how to create a :doc:`Metadata <api/generated/ibridges.meta.MetaData>` object from a data object or collection.

The Metadata object
--------------------
The MetaData class
------------------

.. code-block:: python
Expand All @@ -17,17 +17,30 @@ The Metadata object
session = interactive_auth()
meta = IrodsPath(session, "~", "collection_or_dataobject").meta
# Show all metadata entries with print.
print(meta)
With the object :code:`meta` we can now access and manipulate the metadata of the data object.

The MetaDataItem class
----------------------

As explained above, the metadata of a collection or dataobject can have multiple entries. You can iterate over
these entries as follows:

.. code-block:: python
for item in meta:
print(item.key, item.value, item.units)
Add metadata
------------
To add metadata, you always need to provide a key and a value, the units are optional and can be left out.

.. code-block:: python
meta.add('NewKey', 'NewValue', 'NewUnit')
print(meta)
.. note::
You can have several metadata entries with the same key but different values and units,
Expand All @@ -46,6 +59,51 @@ same key first. This mirrors the implementation of the `iCommands <https://rdm-d
meta.set('ExistingKey', 'Value', 'Unit')
Find metadata items
-------------------

If you want to find all items with a certain key/value/units, you can use the ``find_all`` method
which returns a list of items:

.. code-block:: python
# Find all metadata items with key "some_key".
items = meta.find_all(key="some_key")
# Find all metadata items with value "some_value".
items = meta.find_all(value="some_value")
# Find all metadata items with some units "some_units".
items = meta.find_all(units="some_units")
# Find all metadata items with key == "some_key" and value == "some_value"
items = meta.find_all(key="some_key", value="some_value")
If you are searching for one specific metadata item, then you can also use the following notation,
which will either give back one metadata item, raise a ``KeyError`` if no item matches the criteria, or
a ``ValueError`` if more than one value matches the criteria:

.. code-block:: python
item = meta["some_key"]
item = meta["some_key", "some_value", "some_units"]
Modify metadata items
---------------------

You can also rename the ``key``, ``value`` and ``units`` of a metadata item, by setting it to a new value:

.. code-block:: python
item = metadata["some_key"]
item.key = "new_key"
item.value = "new_value"
item.units = "new_units"
If you are trying to rename the metadata item so that it would overwrite an existing metadata item,
ibridges will throw an error.

Delete metadata
---------------
Below are examples on how to delete metadata entries:
Expand Down
Loading

0 comments on commit 1a8a681

Please sign in to comment.