Skip to content

Commit

Permalink
Merge pull request #178 from kjsanger/feature/avu-query-operators
Browse files Browse the repository at this point in the history
Add support for AVU query operators
  • Loading branch information
kjsanger authored Apr 25, 2024
2 parents a119e91 + 1b77bab commit fdf8a44
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ collection and data object metadata to return combined results.

assert irods.query_metadata(AVU("experiment_id", "experiment_1")) == [obj]

The default query operator is `=`. You can specify a different operator for each
AVU used to define a query by setting its `operator` attribute in any of the query
methods.

assert DataObject.query_metadata(AVU("experiment_id", "experiment%", operator="like")) == [obj]

assert irods.query_metadata(AVU("experiment_id", "experiment%", operator="like")) == [obj]

### Pools

`partisan` uses a small pool (the default is 4) of `BatonClient` instances to
Expand Down
21 changes: 20 additions & 1 deletion src/partisan/irods.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Baton:
ATTRIBUTE = "attribute"
VALUE = "value"
UNITS = "units"
OPERATOR = "operator"

COLL = "collection"
OBJ = "data_object"
Expand Down Expand Up @@ -828,7 +829,12 @@ class AVU(object):
"""The attribute history suffix"""

def __init__(
self, attribute: Any, value: Any, units: str = None, namespace: str = None
self,
attribute: Any,
value: Any,
units: str = None,
namespace: str = None,
operator: str = None,
):
"""Create a new AVU instance.
Expand All @@ -841,13 +847,18 @@ def __init__(
namespace: The namespace (prefix) to be used for the attribute. Optional,
but if supplied, must be the same as any existing namespace on the
attribute string.
operator: The operator to use if the AVU is used as a query argument.
Optional, defaults to '='. Must be one of the available iRODS
query operators. Operators are not considered when comparing AVUs.
"""
if attribute is None:
raise ValueError("AVU attribute may not be None")
if value is None:
raise ValueError("AVU value may not be None")
if namespace is None:
namespace = ""
if operator is None:
operator = "="

attr = str(attribute)
value = str(value)
Expand Down Expand Up @@ -888,6 +899,7 @@ def __init__(
self._attribute = attr
self._value = value
self._units = units
self._operator = operator

@classmethod
def collate(cls, *avus: AVU) -> Dict[str : List[AVU]]:
Expand Down Expand Up @@ -986,6 +998,11 @@ def units(self):
"""The units associated with the attribute. Units may be None."""
return self._units

@property
def operator(self):
"""The operator associated with the AVU. The default is '='."""
return self._operator

def with_namespace(self, namespace: str):
"""make a new copy of this AVU with the specified namespace.
Expand Down Expand Up @@ -2526,6 +2543,8 @@ def default(self, o: Any) -> Any:
enc = {Baton.ATTRIBUTE: o.attribute, Baton.VALUE: o.value}
if o.units:
enc[Baton.UNITS] = o.units
if o.operator:
enc[Baton.OPERATOR] = o.operator
return enc

if isinstance(o, Permission):
Expand Down
12 changes: 12 additions & 0 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ def test_data_object_examples(self, ont_gridion, tmp_path):
# collection and data object metadata to return combined results.
assert irods.query_metadata(AVU("experiment_id", "experiment_1")) == [obj]

# The default query operator is `=`. You can specify a different operator for
# each AVU used to define a query by setting its `operator` attribute in any of
# the query methods.

assert DataObject.query_metadata(
AVU("experiment_id", "experiment%", operator="like")
) == [obj]

assert irods.query_metadata(
AVU("experiment_id", "experiment%", operator="like")
) == [obj]

def test_pool_examples(self, ont_gridion):
# partisan uses a small pool (the default is 4) of BatonClient instances to
# serve requests. This pool is created automatically and is passed to the
Expand Down
12 changes: 11 additions & 1 deletion tests/test_irods.py
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,9 @@ def test_query_meta_collection(self, annotated_collection, annotated_data_object
assert [Collection(annotated_collection)] == Collection.query_metadata(
AVU("attr1", "value1"), AVU("attr2", "value2"), AVU("attr3", "value3")
)
assert [Collection(annotated_collection)] == Collection.query_metadata(
AVU("attr1", "value%", operator="like")
)

@m.describe("Query DataObject namespace")
@m.context("When a DataObject has metadata")
Expand All @@ -1167,10 +1170,13 @@ def test_query_meta_data_object(self, annotated_collection, annotated_data_objec
assert [DataObject(annotated_data_object)] == DataObject.query_metadata(
AVU("attr1", "value1"), AVU("attr2", "value2"), AVU("attr3", "value3")
)
assert [DataObject(annotated_data_object)] == DataObject.query_metadata(
AVU("attr1", "value%", operator="like")
)

@m.describe("Query both DataObject and Collection namespaces")
@m.context("When a DataObjects and Collections have metadata")
@m.it("Can be queried by that metadata, only returning everything")
@m.it("Can be queried by that metadata, returning everything")
def test_query_meta_all(self, annotated_collection, annotated_data_object):
assert [] == query_metadata(AVU("no_such_attr1", "no_such_value1"))
assert [
Expand All @@ -1187,6 +1193,10 @@ def test_query_meta_all(self, annotated_collection, annotated_data_object):
] == query_metadata(
AVU("attr1", "value1"), AVU("attr2", "value2"), AVU("attr3", "value3")
)
assert [
Collection(annotated_collection),
DataObject(annotated_data_object),
] == query_metadata(AVU("attr1", "value%", operator="like"))


@m.describe("Test special paths (quotes, spaces)")
Expand Down

0 comments on commit fdf8a44

Please sign in to comment.