Source code for pykechain.models.property_reference

import os
from typing import Any, List, Optional, Union

from pykechain.defaults import PARTS_BATCH_LIMIT
from pykechain.enums import (
    ContextGroup,
    ScopeReferenceColumns,
    StoredFileCategory,
    StoredFileClassification,
)
from pykechain.exceptions import IllegalArgumentError
from pykechain.models import Activity, Scope, user
from pykechain.models.base_reference import (
    _ReferenceProperty,
    _ReferencePropertyInScope,
)
from pykechain.models.context import Context
from pykechain.models.form import Form
from pykechain.models.input_checks import check_enum
from pykechain.models.stored_file import StoredFile
from pykechain.models.validators import SingleReferenceValidator
from pykechain.models.value_filter import ScopeFilter
from pykechain.models.workflow import Status
from pykechain.utils import get_in_chunks, uniquify


[docs] class ActivityReferencesProperty(_ReferencePropertyInScope): """A virtual object representing a KE-chain Activity References property. .. versionadded:: 3.7 """ REFERENCED_CLASS = Activity def _retrieve_objects(self, **kwargs) -> List[Activity]: """ Retrieve a list of Activities. :param kwargs: optional inputs :return: list of Activity2 objects """ activities = [] for activity_json in self._value: activity = Activity(client=self._client, json=activity_json) activity.refresh() # To populate the object with all expected data activities.append(activity) return activities
[docs] class ScopeReferencesProperty(_ReferenceProperty): """A virtual object representing a KE-chain Scope References property. .. versionadded: 3.9 """ REFERENCED_CLASS = Scope def _retrieve_objects(self, **kwargs) -> List[Scope]: """ Retrieve a list of Scopes. :param kwargs: optional inputs :return: list of Scope2 objects """ scope_ids = self._validate_values() scopes = [] if scope_ids: scopes = list() for chunk in get_in_chunks(scope_ids, PARTS_BATCH_LIMIT): scopes.extend( list(self._client.scopes(id__in=",".join(chunk), status=None)) ) return scopes
[docs] def set_prefilters( self, prefilters: List[ScopeFilter] = None, clear: Optional[bool] = False, ) -> None: """ Set pre-filters on the scope reference property. :param prefilters: list of Scope Filter objects :type prefilters: list :param clear: whether all existing pre-filters should be cleared. (default = False) :type clear: bool :return: None """ if prefilters is not None: if not isinstance(prefilters, list) or not all( isinstance(pf, ScopeFilter) for pf in prefilters ): raise IllegalArgumentError( f"`prefilters` must be a list of ScopeFilter objects, `{prefilters}` is not." ) else: prefilters = [] if not clear: list_of_prefilters = ScopeFilter.parse_options(options=self._options) else: list_of_prefilters = list() list_of_prefilters += prefilters # Only update the options if there are any prefilters to be set, or if the original filters have to overwritten if list_of_prefilters or clear: self._options.update(ScopeFilter.write_options(filters=list_of_prefilters)) self.edit(options=self._options)
[docs] def get_prefilters(self) -> List[ScopeFilter]: """ Return a list of ScopeFilter objects currently configured on the property. :return: list of ScopeFilter objects :rtype list """ return ScopeFilter.parse_options(self._options)
[docs] def set_active_filter_switch(self, switch_visible: bool): """ Set the switch between active and inactive scopes on the scope reference property. :param switch_visible: trigger the switch of showing active or inactive scopes :type switch_visible: bool """ self._options.update({"show_active_status_filter": switch_visible}) self.edit(options=self._options)
[docs] def set_columns(self, list_of_columns: List[ScopeReferenceColumns] = None): """ Set the columns visible inside the Scope selection dialog. :param list_of_columns: all the columns possible of a Scope :type list_of_columns: List of columns """ self._options.update({"columns": list_of_columns}) self.edit(options=self._options)
[docs] class UserReferencesProperty(_ReferenceProperty): """A virtual object representing a KE-chain User References property. .. versionadded: 3.9 """ REFERENCED_CLASS = user.User def _validate_values(self) -> List[str]: """ Check if the `_value` attribute has valid content. :return list of UUIDs: :rtype list """ if not self._value: return [] object_ids = [] for value in self._value: if isinstance(value, dict) and "pk" in value or "id" in value: pk = str(value.get("pk", value.get("id"))) object_ids.append(pk) elif isinstance(value, (int, str)): object_ids.append(str(value)) else: # pragma: no cover raise ValueError( f'Value "{value}" must be a dict with field `pk` or a UUID.' ) return object_ids def _retrieve_objects(self, **kwargs) -> List[user.User]: """ Retrieve a list of Users. :param kwargs: optional inputs :return: list of User objects """ user_ids = self._validate_values() users = [] if user_ids: users = list() for chunk in get_in_chunks(user_ids, PARTS_BATCH_LIMIT): users.extend(list(self._client.users(id__in=",".join(chunk)))) return users
[docs] def value_ids(self) -> Optional[List[int]]: """ Retrieve the referenced object UUIDs only. :return: list of UUIDs :rtype list """ return [value.get("pk") for value in self._value] if self.has_value() else None
[docs] class FormReferencesProperty(_ReferencePropertyInScope): """A virtual object representing a KE-chain Form References property. .. versionadded:: 3.7 """ REFERENCED_CLASS = Form def _retrieve_objects(self, **kwargs) -> List[Form]: """ Retrieve a list of Forms. :param kwargs: optional inputs :return: list of Form objects """ forms = [] for forms_json in self._value: form = Form(client=self._client, json=forms_json) form.refresh() # To populate the object with all expected data forms.append(form) return forms
[docs] class ContextReferencesProperty(_ReferencePropertyInScope): """A virtual object representing a KE-chain Context References property. .. versionadded:: 3.7 """ REFERENCED_CLASS = Context
[docs] def set_prefilters( self, prefilters: Optional = None, clear: Optional[bool] = False, context_group: Optional[ContextGroup] = None, ) -> None: """ Set pre-filters on the Context reference property. On the Context only a context_group prefilter can be set to a single ContextGroup. :param prefilters: Not used :param clear: whether all existing pre-filters should be cleared. (default = False) :param context_group: An optional ContextGroup to prefilter on. One single prefilter is allowed. :return: None """ prefilters_dict = self._options.get("prefilters", dict()) if prefilters: raise IllegalArgumentError( "`prefilters` argument is unused. Use `context_group` instead." ) if clear: prefilters_dict = dict() if context_group: context_group = check_enum(context_group, ContextGroup, "context_group") prefilters_dict = {"context_group": context_group} if context_group or clear: self._options.update({"prefilters": prefilters_dict})
def _retrieve_objects(self, **kwargs) -> List[Context]: """ Retrieve a list of Contexts. :param kwargs: optional inputs :return: list of Context objects """ contexts = [] for contexts_json in self._value: context = Context(client=self._client, json=contexts_json) context.refresh() # To populate the object with all expected data contexts.append(context) return contexts
[docs] class StatusReferencesProperty(_ReferenceProperty): """A virtual object representing a KE-chain Status References property. .. versionadded:: 3.19 """ REFERENCED_CLASS = Status def _retrieve_objects(self, **kwargs) -> List[Status]: """ Retrieve a list of Statuses. :param kwargs: optional inputs :return: list of Status objects """ statuses = [] for status_json in self._value: status = Status(client=self._client, json=status_json) status.refresh() # To populate the object with all expected data statuses.append(status) return statuses
class StoredFilesReferencesProperty(_ReferenceProperty): """A virtual object representing a KE-chain StoredFile References property.""" REFERENCED_CLASS = StoredFile def _retrieve_objects(self, **kwargs) -> List[StoredFile]: """ Retrieve a list of StoredFile. :param kwargs: optional inputs :return: list of StoredFile objects """ if self._value: stored_files_ids = [sf.get("id") for sf in self._value] return self._client.stored_files(id__in=",".join(stored_files_ids)) def clear(self) -> None: """ Clear the stored files from the value of the property. Introduced in order to minimize the effect on custom scripts when converting from `AttachmentProperty` to `StoredFileReferenceProperty`. """ if self.value: self.value = [] @property def filename(self) -> Optional[Union[str, list]]: """Filename or list of filenames of the file(s) stored in the property.""" if self.value: if len(self.value) == 1: return self.value[0].filename else: return [stored_file.filename for stored_file in self.value] else: return None def download(self, directory: str, **kwargs): """Download stored files from the StoredFileReferenceProperty. Downloads multiple files in the provided directory and names them according to the filename. If multiple files have the same name, it makes them unique. :param directory: Directory path :type directory: basestring """ for stored_file in self.value: filename = uniquify(os.path.join(directory, stored_file.filename)) stored_file.save_as(filename=filename, **kwargs) def upload(self, data: Any) -> None: """Upload a stored file to the StoredFileReferenceProperty. :param data: File path :type data: basestring :raises APIError: When unable to upload the file to KE-chain :raises OSError: When the path to the file is incorrect or file could not be found """ filename = os.path.basename(data) new_stored_file = self._client.create_stored_file( name=filename, scope=self.scope, classification=StoredFileClassification.SCOPED, category=StoredFileCategory.REFERENCED, filepath=data, ) if self.has_validator(SingleReferenceValidator): # we want to replace value self.value = [new_stored_file] else: if self.value: self.value.append(new_stored_file) else: self.value = [new_stored_file] class SignatureProperty(_ReferenceProperty): """A virtual object representing a KE-chain StoredFile References property.""" REFERENCED_CLASS = StoredFile @property def value(self) -> Optional[StoredFile]: """ Retrieve the signature of this signature property. :return: an optional `StoredFile` object containing the signature. :rtype StoredFile """ if not self._value: return None elif not self._cached_values: self._cached_values = self._retrieve_object() return self._cached_values @value.setter def value(self, value: Any) -> None: # This serialize_value helper function returns a list of value value: List[Any] = self.serialize_value(value) if value and isinstance(value, list): value = value[0] # de-list this temp_value if self.use_bulk_update: self._pend_update(dict(value=value)) self._value = dict(id=value) if isinstance(value, str) else None self._cached_values = None else: self._put_value(value) def _retrieve_object(self, **kwargs) -> StoredFile: return self._retrieve_objects(**kwargs) def _retrieve_objects(self, **kwargs) -> StoredFile: """ Retrieve a StoredFile which contains the signature. :param kwargs: optional inputs :return: list of StoredFile objects """ if self._value: stored_files_id = self._value.get("id") return self._client.stored_file(id=stored_files_id) def clear(self) -> None: """ Clear the signature from the value of the property. Introduced in order to minimize the effect on custom scripts when converting from `AttachmentProperty` to `StoredFileReferenceProperty`. """ if self.value: self.value = None @property def filename(self) -> Optional[Union[str, list]]: """Filename of the file stored in the signature property.""" if self.value: return self.value.filename else: return None def download(self, directory: str, **kwargs): """Download stored files from the SignatureProperty. Downloads multiple files in the provided directory and names them according to the filename. If multiple files have the same name, it makes them unique. :param directory: Directory path :type directory: basestring """ stored_file = self.value filename = uniquify(os.path.join(directory, stored_file.filename)) stored_file.save_as(filename=filename, **kwargs) def upload(self, data: Any) -> None: """Upload a stored file to the SignatureProperty. :param data: File path :type data: basestring :raises APIError: When unable to upload the file to KE-chain :raises OSError: When the path to the file is incorrect or file could not be found """ filename = os.path.basename(data) stored_file = self._client.create_stored_file( name=filename, scope=self.scope, classification=StoredFileClassification.SCOPED, category=StoredFileCategory.REFERENCED, filepath=data, ) self.value = stored_file