#-----------------------------------------------------------------------------# Copyright (c) 2012 - 2022, Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------""""""#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsfromcollections.abcimport(Container,Iterable,Mapping,Sequence,Sized,)fromtypingimportTYPE_CHECKING,Any# Bokeh importsfrom...util.serializationimportdecode_base64_dict,transform_column_source_datafrom._sphinximportproperty_link,register_type_link,type_linkfrom.basesimportContainerProperty,DeserializationErrorfrom.descriptorsimportColumnDataPropertyDescriptorfrom.enumimportEnumfrom.numericimportIntfrom.singletonsimportUndefinedfrom.wrappersimportPropertyValueColumnData,PropertyValueDict,PropertyValueListifTYPE_CHECKING:from...document.eventsimportDocumentPatchedEvent#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('Array','ColumnData','Dict','List','RelativeDelta','RestrictedDict','Seq','Tuple',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------
[docs]classSeq(ContainerProperty):""" Accept non-string ordered sequences of values, e.g. list, tuple, array. """def__init__(self,item_type,default=Undefined,help=None)->None:self.item_type=self._validate_type_param(item_type)super().__init__(default=default,help=help)def__str__(self)->str:returnf"{self.__class__.__name__}({self.item_type})"@propertydeftype_params(self):return[self.item_type]deffrom_json(self,json,*,models=None):ifisinstance(json,list):returnself._new_instance([self.item_type.from_json(item,models=models)foriteminjson])else:raiseDeserializationError(f"{self} expected a list, got {json}")defvalidate(self,value,detail=True):super().validate(value,True)ifself._is_seq(value)andall(self.item_type.is_valid(item)foriteminvalue):returnifself._is_seq(value):invalid=[]foriteminvalue:ifnotself.item_type.is_valid(item):invalid.append(item)msg=""ifnotdetailelsef"expected an element of {self}, got seq with invalid items {invalid!r}"raiseValueError(msg)msg=""ifnotdetailelsef"expected an element of {self}, got {value!r}"raiseValueError(msg)@classmethoddef_is_seq(cls,value):return((isinstance(value,Sequence)orcls._is_seq_like(value))andnotisinstance(value,str))@classmethoddef_is_seq_like(cls,value):return(isinstance(value,(Container,Sized,Iterable))andhasattr(value,"__getitem__")# NOTE: this is what makes it disallow set typeandnotisinstance(value,Mapping))def_new_instance(self,value):returnvalue
[docs]classList(Seq):""" Accept Python list values. """def__init__(self,item_type,default=[],help=None)->None:# todo: refactor to not use mutable objects as default values.# Left in place for now because we want to allow None to express# optional values. Also in Dict.super().__init__(item_type,default=default,help=help)defwrap(self,value):""" Some property types need to wrap their values in special containers, etc. """ifisinstance(value,list):ifisinstance(value,PropertyValueList):returnvalueelse:returnPropertyValueList(value)else:returnvalue@classmethoddef_is_seq(cls,value):returnisinstance(value,list)
[docs]classDict(ContainerProperty):""" Accept Python dict values. If a default value is passed in, then a shallow copy of it will be used for each new use of this property. """def__init__(self,keys_type,values_type,default={},help=None)->None:self.keys_type=self._validate_type_param(keys_type)self.values_type=self._validate_type_param(values_type)super().__init__(default=default,help=help)def__str__(self)->str:returnf"{self.__class__.__name__}({self.keys_type}, {self.values_type})"@propertydeftype_params(self):return[self.keys_type,self.values_type]deffrom_json(self,json,*,models=None):ifisinstance(json,dict):return{self.keys_type.from_json(key,models=models):self.values_type.from_json(value,models=models)forkey,valueinjson.items()}else:raiseDeserializationError(f"{self} expected a dict, got {json}")defvalidate(self,value,detail=True):super().validate(value,detail)key_is_valid=self.keys_type.is_validvalue_is_valid=self.values_type.is_validifisinstance(value,dict)andall(key_is_valid(key)andvalue_is_valid(val)forkey,valinvalue.items()):returnmsg=""ifnotdetailelsef"expected an element of {self}, got {value!r}"raiseValueError(msg)defwrap(self,value):""" Some property types need to wrap their values in special containers, etc. """ifisinstance(value,dict):ifisinstance(value,PropertyValueDict):returnvalueelse:returnPropertyValueDict(value)else:returnvalue
[docs]classColumnData(Dict):""" Accept a Python dictionary suitable as the ``data`` attribute of a :class:`~bokeh.models.sources.ColumnDataSource`. This class is a specialization of ``Dict`` that handles efficiently encoding columns that are NumPy arrays. """defmake_descriptors(self,base_name):""" Return a list of ``ColumnDataPropertyDescriptor`` instances to install on a class, in order to delegate attribute access to this property. Args: base_name (str) : the name of the property these descriptors are for Returns: list[ColumnDataPropertyDescriptor] The descriptors returned are collected by the ``MetaHasProps`` metaclass and added to ``HasProps`` subclasses during class creation. """return[ColumnDataPropertyDescriptor(base_name,self)]deffrom_json(self,json,*,models=None):""" Decodes column source data encoded as lists or base64 strings. """ifnotisinstance(json,dict):raiseDeserializationError(f"{self} expected a dict, got {json}")new_data={}forkey,valueinjson.items():key=self.keys_type.from_json(key,models=models)ifisinstance(value,dict)and'__ndarray__'invalue:new_data[key]=decode_base64_dict(value)elifisinstance(value,list)andany(isinstance(el,dict)and'__ndarray__'inelforelinvalue):new_list=[]forelinvalue:ifisinstance(el,dict)and'__ndarray__'inel:el=decode_base64_dict(el)elifisinstance(el,list):el=self.values_type.from_json(el)new_list.append(el)new_data[key]=new_listelse:new_data[key]=self.values_type.from_json(value,models=models)returnnew_datadef_hinted_value(self,value:Any,hint:DocumentPatchedEvent|None)->Any:from...document.eventsimportColumnDataChangedEvent,ColumnsStreamedEventifisinstance(hint,ColumnDataChangedEvent):return{col:hint.column_source.data[col]forcolinhint.cols}ifisinstance(hint,ColumnsStreamedEvent):returnhint.datareturnvaluedefserialize_value(self,value):returntransform_column_source_data(value)defwrap(self,value):""" Some property types need to wrap their values in special containers, etc. """ifisinstance(value,dict):ifisinstance(value,PropertyValueColumnData):returnvalueelse:returnPropertyValueColumnData(value)else:returnvalue
[docs]classTuple(ContainerProperty):""" Accept Python tuple values. """def__init__(self,tp1,tp2,*type_params,**kwargs)->None:self._type_params=list(map(self._validate_type_param,(tp1,tp2)+type_params))super().__init__(default=kwargs.get("default",Undefined),help=kwargs.get("help"))def__str__(self)->str:item_types=", ".join(str(x)forxinself.type_params)returnf"{self.__class__.__name__}({item_types})"@propertydeftype_params(self):returnself._type_paramsdeffrom_json(self,json,*,models=None):ifisinstance(json,list):returntuple(type_param.from_json(item,models=models)fortype_param,iteminzip(self.type_params,json))else:raiseDeserializationError(f"{self} expected a list, got {json}")defvalidate(self,value,detail=True):super().validate(value,detail)ifisinstance(value,(tuple,list))andlen(self.type_params)==len(value):ifall(type_param.is_valid(item)fortype_param,iteminzip(self.type_params,value)):returnmsg=""ifnotdetailelsef"expected an element of {self}, got {value!r}"raiseValueError(msg)deftransform(self,value):""" Change the value into a JSON serializable format. """returntuple(typ.transform(x)for(typ,x)inzip(self.type_params,value))defserialize_value(self,value):""" Change the value into a JSON serializable format. """returntuple(typ.serialize_value(x)for(typ,x)inzip(self.type_params,value))
[docs]classRelativeDelta(Dict):""" Accept RelativeDelta dicts for time delta values. """def__init__(self,default={},help=None)->None:keys=Enum("years","months","days","hours","minutes","seconds","microseconds")values=Intsuper().__init__(keys,values,default=default,help=help)def__str__(self)->str:returnself.__class__.__name__
[docs]classRestrictedDict(Dict):""" Check for disallowed key(s). """def__init__(self,keys_type,values_type,disallow,default={},help=None)->None:self._disallow=set(disallow)super().__init__(keys_type=keys_type,values_type=values_type,default=default,help=help)defvalidate(self,value,detail=True):super().validate(value,detail)error_keys=self._disallow&value.keys()iferror_keys:msg=""ifnotdetailelsef"Disallowed keys: {error_keys!r}"raiseValueError(msg)
#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------@register_type_link(Dict)def_sphinx_type_dict(obj):returnf"{property_link(obj)}({type_link(obj.keys_type)}, {type_link(obj.values_type)})"@register_type_link(Seq)def_sphinx_type_seq(obj):returnf"{property_link(obj)}({type_link(obj.item_type)})"@register_type_link(Tuple)def_sphinx_type_tuple(obj):item_types=", ".join(type_link(x)forxinobj.type_params)returnf"{property_link(obj)}({item_types})"