#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2019, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
'''
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Standard library imports
from bokeh.util.future import collections_abc # goes away with py2
# External imports
from six import string_types, iteritems
# Bokeh imports
from ...util.serialization import decode_base64_dict, transform_column_source_data
from .bases import ContainerProperty, DeserializationError
from .descriptors import ColumnDataPropertyDescriptor
from .enum import Enum
from .numeric import Int
from .wrappers import PropertyValueColumnData, PropertyValueDict, PropertyValueList
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------
__all__ = (
'Array',
'ColumnData',
'Dict',
'List',
'RelativeDelta',
'Seq',
'Tuple',
)
#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------
[docs]class Seq(ContainerProperty):
''' Accept non-string ordered sequences of values, e.g. list, tuple, array.
'''
def __init__(self, item_type, default=None, help=None):
self.item_type = self._validate_type_param(item_type)
super(Seq, self).__init__(default=default, help=help)
def __str__(self):
return "%s(%s)" % (self.__class__.__name__, self.item_type)
@property
def type_params(self):
return [self.item_type]
def from_json(self, json, models=None):
if json is None:
return None
elif isinstance(json, list):
return self._new_instance([ self.item_type.from_json(item, models) for item in json ])
else:
raise DeserializationError("%s expected a list or None, got %s" % (self, json))
def validate(self, value, detail=True):
super(Seq, self).validate(value, True)
if value is not None:
if not (self._is_seq(value) and all(self.item_type.is_valid(item) for item in value)):
if self._is_seq(value):
invalid = []
for item in value:
if not self.item_type.is_valid(item):
invalid.append(item)
msg = "" if not detail else "expected an element of %s, got seq with invalid items %r" % (self, invalid)
raise ValueError(msg)
else:
msg = "" if not detail else "expected an element of %s, got %r" % (self, value)
raise ValueError(msg)
@classmethod
def _is_seq(cls, value):
return ((isinstance(value, collections_abc.Sequence) or cls._is_seq_like(value)) and
not isinstance(value, string_types))
@classmethod
def _is_seq_like(cls, value):
return (isinstance(value, (collections_abc.Container, collections_abc.Sized, collections_abc.Iterable))
and hasattr(value, "__getitem__") # NOTE: this is what makes it disallow set type
and not isinstance(value, collections_abc.Mapping))
def _new_instance(self, value):
return value
def _sphinx_type(self):
return self._sphinx_prop_link() + "( %s )" % self.item_type._sphinx_type()
[docs]class List(Seq):
''' Accept Python list values.
'''
def __init__(self, item_type, default=[], help=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(List, self).__init__(item_type, default=default, help=help)
@classmethod
def wrap(cls, value):
''' Some property types need to wrap their values in special containers, etc.
'''
if isinstance(value, list):
if isinstance(value, PropertyValueList):
return value
else:
return PropertyValueList(value)
else:
return value
@classmethod
def _is_seq(cls, value):
return isinstance(value, list)
[docs]class Array(Seq):
''' Accept NumPy array values.
'''
@classmethod
def _is_seq(cls, value):
import numpy as np
return isinstance(value, np.ndarray)
def _new_instance(self, value):
import numpy as np
return np.array(value)
[docs]class Dict(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):
self.keys_type = self._validate_type_param(keys_type)
self.values_type = self._validate_type_param(values_type)
super(Dict, self).__init__(default=default, help=help)
def __str__(self):
return "%s(%s, %s)" % (self.__class__.__name__, self.keys_type, self.values_type)
@property
def type_params(self):
return [self.keys_type, self.values_type]
def from_json(self, json, models=None):
if json is None:
return None
elif isinstance(json, dict):
return { self.keys_type.from_json(key, models): self.values_type.from_json(value, models) for key, value in iteritems(json) }
else:
raise DeserializationError("%s expected a dict or None, got %s" % (self, json))
def validate(self, value, detail=True):
super(Dict, self).validate(value, detail)
if value is not None:
if not (isinstance(value, dict) and \
all(self.keys_type.is_valid(key) and self.values_type.is_valid(val) for key, val in iteritems(value))):
msg = "" if not detail else "expected an element of %s, got %r" % (self, value)
raise ValueError(msg)
@classmethod
def wrap(cls, value):
''' Some property types need to wrap their values in special containers, etc.
'''
if isinstance(value, dict):
if isinstance(value, PropertyValueDict):
return value
else:
return PropertyValueDict(value)
else:
return value
def _sphinx_type(self):
return self._sphinx_prop_link() + "( %s, %s )" % (self.keys_type._sphinx_type(), self.values_type._sphinx_type())
[docs]class ColumnData(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.
'''
def make_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) ]
def from_json(self, json, models=None):
''' Decodes column source data encoded as lists or base64 strings.
'''
if json is None:
return None
elif not isinstance(json, dict):
raise DeserializationError("%s expected a dict or None, got %s" % (self, json))
new_data = {}
for key, value in json.items():
key = self.keys_type.from_json(key, models)
if isinstance(value, dict) and '__ndarray__' in value:
new_data[key] = decode_base64_dict(value)
elif isinstance(value, list) and any(isinstance(el, dict) and '__ndarray__' in el for el in value):
new_list = []
for el in value:
if isinstance(el, dict) and '__ndarray__' in el:
el = decode_base64_dict(el)
elif isinstance(el, list):
el = self.values_type.from_json(el)
new_list.append(el)
new_data[key] = new_list
else:
new_data[key] = self.values_type.from_json(value, models)
return new_data
def serialize_value(self, value):
return transform_column_source_data(value)
@classmethod
def wrap(cls, value):
''' Some property types need to wrap their values in special containers, etc.
'''
if isinstance(value, dict):
if isinstance(value, PropertyValueColumnData):
return value
else:
return PropertyValueColumnData(value)
else:
return value
[docs]class Tuple(ContainerProperty):
''' Accept Python tuple values.
'''
def __init__(self, tp1, tp2, *type_params, **kwargs):
self._type_params = list(map(self._validate_type_param, (tp1, tp2) + type_params))
super(Tuple, self).__init__(default=kwargs.get("default"), help=kwargs.get("help"))
def __str__(self):
return "%s(%s)" % (self.__class__.__name__, ", ".join(map(str, self.type_params)))
@property
def type_params(self):
return self._type_params
def from_json(self, json, models=None):
if json is None:
return None
elif isinstance(json, list):
return tuple(type_param.from_json(item, models) for type_param, item in zip(self.type_params, json))
else:
raise DeserializationError("%s expected a list or None, got %s" % (self, json))
def validate(self, value, detail=True):
super(Tuple, self).validate(value, detail)
if value is not None:
if not (isinstance(value, (tuple, list)) and len(self.type_params) == len(value) and \
all(type_param.is_valid(item) for type_param, item in zip(self.type_params, value))):
msg = "" if not detail else "expected an element of %s, got %r" % (self, value)
raise ValueError(msg)
def _sphinx_type(self):
return self._sphinx_prop_link() + "( %s )" % ", ".join(x._sphinx_type() for x in self.type_params)
[docs]class RelativeDelta(Dict):
''' Accept RelativeDelta dicts for time delta values.
'''
def __init__(self, default={}, help=None):
keys = Enum("years", "months", "days", "hours", "minutes", "seconds", "microseconds")
values = Int
super(RelativeDelta, self).__init__(keys, values, default=default, help=help)
def __str__(self):
return self.__class__.__name__
#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------