Source code for pykechain.models.property_multi_reference

import warnings
from typing import Any, Dict, List, Optional, Tuple, Union

from pykechain.defaults import PARTS_BATCH_LIMIT
from pykechain.enums import Category, FilterType, SortTable
from pykechain.models.base_reference import _ReferencePropertyInScope
from pykechain.models.input_checks import check_type
from pykechain.models.part import Part
from pykechain.models.value_filter import PropertyValueFilter
from pykechain.models.widgets.enums import MetaWidget, PropertyReferenceOptions
from pykechain.models.widgets.helpers import (
    _check_excluded_propmodels,
    _check_prefilters,
    _retrieve_object_id,
)
from pykechain.utils import get_in_chunks


[docs] class MultiReferenceProperty(_ReferencePropertyInScope): """A virtual object representing a KE-chain multi-references property. .. versionadded:: 1.14 """ REFERENCED_CLASS = Part def _retrieve_objects(self, **kwargs) -> List[Part]: """ Retrieve a list of Parts. :param kwargs: optional inputs :return: list of Part objects """ part_ids = self._validate_values() parts = [] if part_ids: if self.category == Category.MODEL: parts = [self._client.part(pk=part_ids[0], category=None)] elif self.category == Category.INSTANCE: # Retrieve the referenced model for low-permissions scripts to enable use of the `id__in` key if ( False ): # TODO Check for script permissions in order to skip the model() retrieval models = [None] else: models = self.model().value if models: parts = list() for chunk in get_in_chunks(part_ids, PARTS_BATCH_LIMIT): parts.extend( list( self._client.parts( id__in=",".join(chunk), model=models[0], category=None, ) ) ) return parts
[docs] def choices(self) -> List[Part]: """Retrieve the parts that you can reference for this `MultiReferenceProperty`. This method makes 2 API calls: 1) to retrieve the referenced model, and 2) to retrieve the instances of that model. :return: the :class:`Part`'s that can be referenced as a :class:`~pykechain.model.PartSet`. :raises APIError: When unable to load and provide the choices Example ------- >>> reference_property = project.part('Bike').property('a_multi_reference_property') >>> referenced_part_choices = reference_property.choices() """ possible_choices = list() # Check whether the model of this reference property (possible itself) has a configured value if self.model().has_value(): # If a model is configured, retrieve its ID choices_model_id = self.model()._value[0].get("id") # Determine which parts are filtered out prefilter: Optional[str] = self._options.get(MetaWidget.PREFILTERS, {}).get( MetaWidget.PROPERTY_VALUE_PREFILTER ) # Retrieve all part instances with this model ID possible_choices = self._client.parts( model_id=choices_model_id, property_value=prefilter, ) return possible_choices
[docs] def set_prefilters( self, property_models: List[Union[str, "AnyProperty"]] = None, values: List[Any] = None, filters_type: List[FilterType] = None, prefilters: List[PropertyValueFilter] = None, overwrite: Optional[bool] = False, clear: Optional[bool] = False, validate: Optional[Union[bool, Part]] = True, ) -> None: """ Set the pre-filters on a `MultiReferenceProperty`. :param property_models: `list` of `Property` models (or their IDs) to set pre-filters on :type property_models: list :param values: `list` of values to pre-filter on, value has to match the property type. :type values: list :param filters_type: `list` of filter types per pre-filter, one of :class:`enums.FilterType`, defaults to `FilterType.CONTAINS` :type filters_type: list :param prefilters: `list` of PropertyValueFilter objects :type prefilters: list :param overwrite: whether existing pre-filters should be overwritten, if new filters to the same property are provided as input. Does not remove non-conflicting prefilters. (default = False) :type overwrite: bool :param clear: whether all existing pre-filters should be cleared. (default = False) :type clear: bool :param validate: whether the pre-filters are validated to the referenced model, which can be provided as well :type validate: bool :raises IllegalArgumentError: when the type of the input is provided incorrect. """ if not clear: list_of_prefilters = PropertyValueFilter.parse_options( options=self._options ) else: list_of_prefilters = list() if prefilters is None: new_prefilters = { PropertyReferenceOptions.PREFILTER_PROPERTY_MODELS: property_models, PropertyReferenceOptions.PREFILTER_VALUES: values, PropertyReferenceOptions.PREFILTER_FILTER_TYPES: filters_type, } else: new_prefilters = prefilters if not validate: part_model = None elif isinstance(validate, Part): part_model = validate else: part_model = self.value[0] verified_prefilters = _check_prefilters( part_model=part_model, prefilters=new_prefilters, ) if ( overwrite ): # Remove pre-filters from the existing prefilters if they match the property model UUID provided_filter_ids = {pf.id for pf in verified_prefilters} list_of_prefilters = [ pf for pf in list_of_prefilters if pf.id not in provided_filter_ids ] list_of_prefilters += verified_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( PropertyValueFilter.write_options(filters=list_of_prefilters) ) self.edit(options=self._options)
[docs] def get_prefilters( self, as_lists: Optional[bool] = False, ) -> Union[List[PropertyValueFilter], Tuple[List[str]]]: """ Retrieve the pre-filters applied to the reference property. :param as_lists: (O) (default = False) If True, the pre-filters are returned as three lists of property model UUIDs, values and filter types. :return: prefilters """ check_type(as_lists, bool, "as_lists") prefilters = PropertyValueFilter.parse_options(options=self._options) if as_lists: property_model_ids = [pf.id for pf in prefilters] values = [pf.value for pf in prefilters] filter_types = [pf.type for pf in prefilters] prefilters = tuple([property_model_ids, values, filter_types]) warnings.warn( "Prefilters will be provided as list of `PropertyValueFilter` objects. " "Separate lists will be deprecated in January 2021.", # TODO Deprecate January 2021 PendingDeprecationWarning, ) return prefilters
[docs] def set_excluded_propmodels( self, property_models: List[Union[str, "AnyProperty"]], overwrite: Optional[bool] = False, validate: Optional[Union[bool, Part]] = True, ) -> None: """ Exclude a list of properties from being visible in the part-shop and modal (pop-up) of the reference property. :param property_models: `list` of Property models (or their IDs) to exclude. :type property_models: list :param overwrite: flag whether to overwrite existing (True) or append (False, default) to existing filter(s). :type overwrite bool :param validate: whether the pre-filters are validated to the referenced model, which can be provided as well :type validate: bool :raises IllegalArgumentError """ if not overwrite: list_of_propmodels_excl = self._options.get( PropertyReferenceOptions.PROPMODELS_EXCLUDED, [] ) else: list_of_propmodels_excl = list() if not validate: part_model = None elif isinstance(validate, Part): part_model = validate else: part_model = self.value[0] list_of_propmodels_excl.extend( _check_excluded_propmodels( part_model=part_model, property_models=property_models, ) ) options_to_set = self._options options_to_set[PropertyReferenceOptions.PROPMODELS_EXCLUDED] = list( set(list_of_propmodels_excl) ) self.edit(options=options_to_set)
[docs] def get_excluded_propmodel_ids(self) -> List[str]: """ Retrieve a list of property model UUIDs which are not visible. :return: list of UUIDs :rtype list """ return self._options.get(PropertyReferenceOptions.PROPMODELS_EXCLUDED, [])
[docs] def set_sorting( self, sort_property: Optional[Union["AnyProperty", str]] = None, sort_name: Optional[Union[bool, str]] = False, sort_direction: Optional[Union[SortTable, str]] = SortTable.ASCENDING, clear: Optional[bool] = False, ) -> None: """ Set sorting on a specific property (or name) of the referenced part model. :param sort_property: The property model on which the part instances are being sorted on :type sort_property: :class:`Property` or UUID :param sort_name: If set to True it will sort on name of the part. It is ignored if sort_property is None :type sort_name: bool :param sort_direction: The direction on which the values of property instances are being sorted on: * ASC (default): Sort in ascending order * DESC: Sort in descending order :type sort_direction: basestring (see :class:`enums.SortTable`) :param clear: whether all existing sorting should be cleared. If set to True, it will ignore all the other parameters :type clear: bool :raises IllegalArgumentError """ if clear: options_to_set = self._options options_to_set.pop(PropertyReferenceOptions.SORTED_COLUMN) options_to_set.pop(PropertyReferenceOptions.SORTED_DIRECTION) self.edit(options=options_to_set) return sort_property_id: str = _retrieve_object_id(obj=sort_property) if not sort_property_id and sort_name: sort_property_id = PropertyReferenceOptions.NAME options_to_set = self._options options_to_set[PropertyReferenceOptions.SORTED_COLUMN] = sort_property_id options_to_set[PropertyReferenceOptions.SORTED_DIRECTION] = sort_direction self.edit(options=options_to_set)
[docs] def get_sorting(self) -> Dict: """ Retrieve the sorted column and sorted direction, if applicable. :return: dict of sorting keys and values :rtype dict """ sorting_options = { PropertyReferenceOptions.SORTED_COLUMN: self._options.get( PropertyReferenceOptions.SORTED_COLUMN ), PropertyReferenceOptions.SORTED_DIRECTION: self._options.get( PropertyReferenceOptions.SORTED_DIRECTION ), } return sorting_options