Newer
Older
Adam Laskowski
committed
# Copyright ETH 2018 - 2023 Zürich, Scientific IT Services
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
Swen Vermeul
committed
from .property import PropertyHolder
Swen Vermeul
committed
from .attribute import AttrHolder
from .utils import VERBOSE
from .definitions import (
get_definition_for_entity,
get_type_for_entity,
get_method_for_entity,
)
from collections import defaultdict
Swen Vermeul
committed
class OpenBisObject:
def __init_subclass__(cls, entity=None, single_item_method_name=None):
"""create a specialized parent class.
The class that inherits from OpenBisObject does not need
to implement its own __init__ method in order to provide the
entity name. Instead, it can pass the entity name as a param:
class XYZ(OpenBisObject, entity="myEntity")
"""
cls._entity = entity
cls._single_item_method_name = single_item_method_name
def __init__(self, openbis_obj, type=None, data=None, props=None, **kwargs):
self.__dict__["openbis"] = openbis_obj
self.__dict__["type"] = type
self.__dict__["p"] = PropertyHolder(openbis_obj, type)
self.__dict__["a"] = AttrHolder(openbis_obj, self._entity, type)
Swen Vermeul
committed
# existing OpenBIS object
if data is not None:
self._set_data(data)
if props is not None:
for key in props:
setattr(self.p, key, props[key])
if kwargs is not None:
for key in kwargs:
setattr(self, key, kwargs[key])
def __dir__(self):
defs = get_definition_for_entity(self.entity)
if self.is_new:
else:
Swen Vermeul
committed
def _set_data(self, data):
# assign the attribute data to self.a by calling it
# (invoking the AttrHolder.__call__ function)
self.a(data)
Swen Vermeul
committed
# put the properties in the self.p namespace (without checking them)
if "properties" in data:
for key, value in data["properties"].items():
Swen Vermeul
committed
self.p.__dict__[key.lower()] = value
Swen Vermeul
committed
# object is already saved to openBIS, so it is not new anymore
Swen Vermeul
committed
@property
def attrs(self):
Swen Vermeul
committed
@property
def space(self):
try:
return self.openbis.get_space(self._space["permId"])
Swen Vermeul
committed
except Exception:
pass
@property
def project(self):
try:
return self.openbis.get_project(self._project["identifier"])
Swen Vermeul
committed
except Exception:
pass
@property
def experiment(self):
try:
return self.openbis.get_experiment(self._experiment["identifier"])
Swen Vermeul
committed
except Exception:
pass
Swen Vermeul
committed
@property
def sample(self):
try:
return self.openbis.get_sample(self._sample["identifier"])
Swen Vermeul
committed
except Exception:
pass
Swen Vermeul
committed
except Exception:
return ""
@property
def permId(self):
try:
except Exception:
return ""
Swen Vermeul
committed
def __getattr__(self, name):
Swen Vermeul
committed
def __setattr__(self, name, value):
if name in ["set_properties", "set_tags", "add_tags"]:
Swen Vermeul
committed
raise ValueError("These are methods which should not be overwritten")
Swen Vermeul
committed
def _repr_html_(self):
"""Print all the assigned attributes (identifier, tags, etc.) in a nicely formatted table. See
AttributeHolder class.
"""
return self.a._repr_html_()
def __repr__(self):
Swen Vermeul
committed
return self.a.__repr__()
def is_marked_to_be_deleted(self):
return self.__dict__.get("mark_to_be_deleted", False)
vkovtun
committed
def delete(self, reason, permanently=False):
"""Delete this openbis entity.
A reason is mandatory to delete any entity.
"""
if not self.data:
return
vkovtun
committed
deletion_id = self.openbis.delete_openbis_entity(
entity=self._entity, objectId=self.data["permId"], reason=reason
print(f"{self._entity} {self.permId} successfully deleted.")
vkovtun
committed
if permanently:
self.openbis.confirm_deletions([deletion_id])
if VERBOSE:
print(f"{self._entity} {self.permId} successfully deleted permanently.")
def _get_single_item_method(self):
single_item_method = None
if self._single_item_method_name:
single_item_method = getattr(self.openbis, self._single_item_method_name)
single_item_method = getattr(self.openbis, "get_" + self.entity)
return single_item_method
def save(self):
get_single_item = self._get_single_item_method()
# check for mandatory properties before saving the object
props = None
if self.props:
for prop_name, prop in self.props._property_names.items():
if prop["mandatory"]:
if (
getattr(self.props, prop_name) is None
or getattr(self.props, prop_name) == ""
):
f"Property '{prop_name}' is mandatory and must not be None"
)
props = self.p._all_props()
# NEW
if self.is_new:
request = self._new_attrs()
if props:
request["params"][1][0]["properties"] = props
resp = self.openbis._post_request(self.openbis.as_v3, request)
print(f"{self.entity} successfully created.")
new_entity_data = get_single_item(resp[0]["permId"], only_data=True)
self._set_data(new_entity_data)
return self
# UPDATE
else:
request = self._up_attrs(method_name=None, permId=self._permId)
if props:
request["params"][1][0]["properties"] = props
resp = self.openbis._post_request(self.openbis.as_v3, request)
print(f"{self.entity} successfully updated.")
new_entity_data = get_single_item(
self.permId, only_data=True, use_cache=False
)
class Transaction:
def __init__(self, *entities):
self.entities = {}
if not entities:
return
for entity in entities:
self.add(entity)
def add(self, entity_obj):
"""Collect the creations or updates of entities.
self.entities = {
"sample": {
{
"create": [...],
"update": [...]
},
},
"dataSet": {
{
"update": [...]
}
}
}
"""
if entity_obj.entity == "dataSet" and entity_obj.is_new:
raise ValueError(
"pyBIS currently does not support transactions for new dataSets yet."
)
if not entity_obj.entity in self.entities:
self.entities[entity_obj.entity] = defaultdict(list)
elif entity_obj.is_marked_to_be_deleted():
self.entities[entity_obj.entity][mode].append(entity_obj)
def commit(self):
"""Merge the individual requests to Make one single request.
For each entity-type and mode (create, update) an individual request will be sent,
as the method name differs.
"""
for entity_type in self.entities:
for mode in self.entities[entity_type]:
request_coll = []
for entity in self.entities[entity_type][mode]:
delete_options = get_type_for_entity(entity_type, "delete")
delete_options["reason"] = self.reason
method = get_method_for_entity(entity_type, "delete")
request = {
"method": method,
"params": [
entity.openbis.token,
[entity.data["permId"]],
delete_options,
],
}
request_coll.append(request)
continue
props = None
if entity.props:
for prop_name, prop in entity.props._property_names.items():
if prop["mandatory"]:
if (
getattr(entity.props, prop_name) is None
or getattr(entity.props, prop_name) == ""
):
raise ValueError(
f"Property '{prop_name}' is mandatory and must not be None"
)
props = entity.p._all_props()
if mode == "create":
request = entity._new_attrs()
if props:
request["params"][1][0]["properties"] = props
elif mode == "update":
request = entity._up_attrs(
method_name=None, permId=entity._permId
)
if props:
request["params"][1][0]["properties"] = props
else:
raise ValueError(f"Unkown mode: {mode}")
request_coll.append(request)
if request_coll:
batch_request = copy.deepcopy(request_coll[0])
for i, request in enumerate(request_coll):
if i == 0:
continue
batch_request["params"][1].append(request["params"][1][0])
resp = entity.openbis._post_request(
entity.openbis.as_v3, batch_request
)
if VERBOSE:
print(f"{i+1} {entity_type}(s) {mode}d.")
# mark every sample as not being new anymore
# and add the permId attribute received by the response
# we assume the response permIds are the same order as we sent them
if resp:
for i, resp_item in enumerate(resp):
if mode == "delete":
continue
self.entities[entity_type][mode][i].a.__dict__[
"_is_new"
] = False
self.entities[entity_type][mode][i].a.__dict__[
"_permId"
] = resp_item
print(f"ERROR: {mode} of {i+1} {entity_type}(s) FAILED")
raise ValueError(err)