diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..abe3e26 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: bdc0d196121379e3ffec847842ab0485 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/about.doctree b/.doctrees/about.doctree new file mode 100644 index 0000000..dbced42 Binary files /dev/null and b/.doctrees/about.doctree differ diff --git a/.doctrees/comparisons.doctree b/.doctrees/comparisons.doctree new file mode 100644 index 0000000..eca69ad Binary files /dev/null and b/.doctrees/comparisons.doctree differ diff --git a/.doctrees/developer_guide.doctree b/.doctrees/developer_guide.doctree new file mode 100644 index 0000000..ab347a4 Binary files /dev/null and b/.doctrees/developer_guide.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 0000000..6109fc0 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/getting_started.doctree b/.doctrees/getting_started.doctree new file mode 100644 index 0000000..8485f5f Binary files /dev/null and b/.doctrees/getting_started.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 0000000..1dbf7c6 Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/reference/index.doctree b/.doctrees/reference/index.doctree new file mode 100644 index 0000000..0d7792e Binary files /dev/null and b/.doctrees/reference/index.doctree differ diff --git a/.doctrees/reference/numbergen.doctree b/.doctrees/reference/numbergen.doctree new file mode 100644 index 0000000..188afd0 Binary files /dev/null and b/.doctrees/reference/numbergen.doctree differ diff --git a/.doctrees/reference/param/generated/param.Action.doctree b/.doctrees/reference/param/generated/param.Action.doctree new file mode 100644 index 0000000..7a97ad7 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Action.doctree differ diff --git a/.doctrees/reference/param/generated/param.Array.doctree b/.doctrees/reference/param/generated/param.Array.doctree new file mode 100644 index 0000000..a5a2d98 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Array.doctree differ diff --git a/.doctrees/reference/param/generated/param.Boolean.doctree b/.doctrees/reference/param/generated/param.Boolean.doctree new file mode 100644 index 0000000..9e1f367 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Boolean.doctree differ diff --git a/.doctrees/reference/param/generated/param.Bytes.doctree b/.doctrees/reference/param/generated/param.Bytes.doctree new file mode 100644 index 0000000..306d73e Binary files /dev/null and b/.doctrees/reference/param/generated/param.Bytes.doctree differ diff --git a/.doctrees/reference/param/generated/param.CalendarDateRange.doctree b/.doctrees/reference/param/generated/param.CalendarDateRange.doctree new file mode 100644 index 0000000..8c06221 Binary files /dev/null and b/.doctrees/reference/param/generated/param.CalendarDateRange.doctree differ diff --git a/.doctrees/reference/param/generated/param.Callable.doctree b/.doctrees/reference/param/generated/param.Callable.doctree new file mode 100644 index 0000000..ee9a68d Binary files /dev/null and b/.doctrees/reference/param/generated/param.Callable.doctree differ diff --git a/.doctrees/reference/param/generated/param.ClassSelector.doctree b/.doctrees/reference/param/generated/param.ClassSelector.doctree new file mode 100644 index 0000000..60830b0 Binary files /dev/null and b/.doctrees/reference/param/generated/param.ClassSelector.doctree differ diff --git a/.doctrees/reference/param/generated/param.Color.doctree b/.doctrees/reference/param/generated/param.Color.doctree new file mode 100644 index 0000000..c805ede Binary files /dev/null and b/.doctrees/reference/param/generated/param.Color.doctree differ diff --git a/.doctrees/reference/param/generated/param.Composite.doctree b/.doctrees/reference/param/generated/param.Composite.doctree new file mode 100644 index 0000000..b480d35 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Composite.doctree differ diff --git a/.doctrees/reference/param/generated/param.DataFrame.doctree b/.doctrees/reference/param/generated/param.DataFrame.doctree new file mode 100644 index 0000000..1a09640 Binary files /dev/null and b/.doctrees/reference/param/generated/param.DataFrame.doctree differ diff --git a/.doctrees/reference/param/generated/param.DateRange.doctree b/.doctrees/reference/param/generated/param.DateRange.doctree new file mode 100644 index 0000000..8989659 Binary files /dev/null and b/.doctrees/reference/param/generated/param.DateRange.doctree differ diff --git a/.doctrees/reference/param/generated/param.Dict.doctree b/.doctrees/reference/param/generated/param.Dict.doctree new file mode 100644 index 0000000..3676d35 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Dict.doctree differ diff --git a/.doctrees/reference/param/generated/param.Dynamic.doctree b/.doctrees/reference/param/generated/param.Dynamic.doctree new file mode 100644 index 0000000..323504d Binary files /dev/null and b/.doctrees/reference/param/generated/param.Dynamic.doctree differ diff --git a/.doctrees/reference/param/generated/param.Event.doctree b/.doctrees/reference/param/generated/param.Event.doctree new file mode 100644 index 0000000..95eb917 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Event.doctree differ diff --git a/.doctrees/reference/param/generated/param.FileSelector.doctree b/.doctrees/reference/param/generated/param.FileSelector.doctree new file mode 100644 index 0000000..deed58e Binary files /dev/null and b/.doctrees/reference/param/generated/param.FileSelector.doctree differ diff --git a/.doctrees/reference/param/generated/param.Filename.doctree b/.doctrees/reference/param/generated/param.Filename.doctree new file mode 100644 index 0000000..32e8260 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Filename.doctree differ diff --git a/.doctrees/reference/param/generated/param.Foldername.doctree b/.doctrees/reference/param/generated/param.Foldername.doctree new file mode 100644 index 0000000..f17099f Binary files /dev/null and b/.doctrees/reference/param/generated/param.Foldername.doctree differ diff --git a/.doctrees/reference/param/generated/param.HookList.doctree b/.doctrees/reference/param/generated/param.HookList.doctree new file mode 100644 index 0000000..7c6a948 Binary files /dev/null and b/.doctrees/reference/param/generated/param.HookList.doctree differ diff --git a/.doctrees/reference/param/generated/param.Integer.doctree b/.doctrees/reference/param/generated/param.Integer.doctree new file mode 100644 index 0000000..db5b0fc Binary files /dev/null and b/.doctrees/reference/param/generated/param.Integer.doctree differ diff --git a/.doctrees/reference/param/generated/param.List.doctree b/.doctrees/reference/param/generated/param.List.doctree new file mode 100644 index 0000000..be197d2 Binary files /dev/null and b/.doctrees/reference/param/generated/param.List.doctree differ diff --git a/.doctrees/reference/param/generated/param.ListSelector.doctree b/.doctrees/reference/param/generated/param.ListSelector.doctree new file mode 100644 index 0000000..6fc934b Binary files /dev/null and b/.doctrees/reference/param/generated/param.ListSelector.doctree differ diff --git a/.doctrees/reference/param/generated/param.Magnitude.doctree b/.doctrees/reference/param/generated/param.Magnitude.doctree new file mode 100644 index 0000000..2069efe Binary files /dev/null and b/.doctrees/reference/param/generated/param.Magnitude.doctree differ diff --git a/.doctrees/reference/param/generated/param.MultiFileSelector.doctree b/.doctrees/reference/param/generated/param.MultiFileSelector.doctree new file mode 100644 index 0000000..61c39b1 Binary files /dev/null and b/.doctrees/reference/param/generated/param.MultiFileSelector.doctree differ diff --git a/.doctrees/reference/param/generated/param.Number.doctree b/.doctrees/reference/param/generated/param.Number.doctree new file mode 100644 index 0000000..acec152 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Number.doctree differ diff --git a/.doctrees/reference/param/generated/param.NumericTuple.doctree b/.doctrees/reference/param/generated/param.NumericTuple.doctree new file mode 100644 index 0000000..857767a Binary files /dev/null and b/.doctrees/reference/param/generated/param.NumericTuple.doctree differ diff --git a/.doctrees/reference/param/generated/param.ParamOverrides.doctree b/.doctrees/reference/param/generated/param.ParamOverrides.doctree new file mode 100644 index 0000000..9d3eae2 Binary files /dev/null and b/.doctrees/reference/param/generated/param.ParamOverrides.doctree differ diff --git a/.doctrees/reference/param/generated/param.Parameter.doctree b/.doctrees/reference/param/generated/param.Parameter.doctree new file mode 100644 index 0000000..c211dbb Binary files /dev/null and b/.doctrees/reference/param/generated/param.Parameter.doctree differ diff --git a/.doctrees/reference/param/generated/param.Parameterized.doctree b/.doctrees/reference/param/generated/param.Parameterized.doctree new file mode 100644 index 0000000..d8b52d5 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Parameterized.doctree differ diff --git a/.doctrees/reference/param/generated/param.ParameterizedFunction.doctree b/.doctrees/reference/param/generated/param.ParameterizedFunction.doctree new file mode 100644 index 0000000..e829cca Binary files /dev/null and b/.doctrees/reference/param/generated/param.ParameterizedFunction.doctree differ diff --git a/.doctrees/reference/param/generated/param.Path.doctree b/.doctrees/reference/param/generated/param.Path.doctree new file mode 100644 index 0000000..e93314d Binary files /dev/null and b/.doctrees/reference/param/generated/param.Path.doctree differ diff --git a/.doctrees/reference/param/generated/param.Range.doctree b/.doctrees/reference/param/generated/param.Range.doctree new file mode 100644 index 0000000..0e97200 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Range.doctree differ diff --git a/.doctrees/reference/param/generated/param.Selector.doctree b/.doctrees/reference/param/generated/param.Selector.doctree new file mode 100644 index 0000000..5755b7c Binary files /dev/null and b/.doctrees/reference/param/generated/param.Selector.doctree differ diff --git a/.doctrees/reference/param/generated/param.Series.doctree b/.doctrees/reference/param/generated/param.Series.doctree new file mode 100644 index 0000000..7fc5bd1 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Series.doctree differ diff --git a/.doctrees/reference/param/generated/param.String.doctree b/.doctrees/reference/param/generated/param.String.doctree new file mode 100644 index 0000000..3e544d9 Binary files /dev/null and b/.doctrees/reference/param/generated/param.String.doctree differ diff --git a/.doctrees/reference/param/generated/param.Tuple.doctree b/.doctrees/reference/param/generated/param.Tuple.doctree new file mode 100644 index 0000000..9c655b5 Binary files /dev/null and b/.doctrees/reference/param/generated/param.Tuple.doctree differ diff --git a/.doctrees/reference/param/generated/param.XYCoordinates.doctree b/.doctrees/reference/param/generated/param.XYCoordinates.doctree new file mode 100644 index 0000000..1b8ff65 Binary files /dev/null and b/.doctrees/reference/param/generated/param.XYCoordinates.doctree differ diff --git a/.doctrees/reference/param/generated/param.batch_watch.doctree b/.doctrees/reference/param/generated/param.batch_watch.doctree new file mode 100644 index 0000000..f95cd9a Binary files /dev/null and b/.doctrees/reference/param/generated/param.batch_watch.doctree differ diff --git a/.doctrees/reference/param/generated/param.concrete_descendents.doctree b/.doctrees/reference/param/generated/param.concrete_descendents.doctree new file mode 100644 index 0000000..7dac581 Binary files /dev/null and b/.doctrees/reference/param/generated/param.concrete_descendents.doctree differ diff --git a/.doctrees/reference/param/generated/param.depends.doctree b/.doctrees/reference/param/generated/param.depends.doctree new file mode 100644 index 0000000..270f9c4 Binary files /dev/null and b/.doctrees/reference/param/generated/param.depends.doctree differ diff --git a/.doctrees/reference/param/generated/param.discard_events.doctree b/.doctrees/reference/param/generated/param.discard_events.doctree new file mode 100644 index 0000000..3c5e39a Binary files /dev/null and b/.doctrees/reference/param/generated/param.discard_events.doctree differ diff --git a/.doctrees/reference/param/generated/param.edit_constant.doctree b/.doctrees/reference/param/generated/param.edit_constant.doctree new file mode 100644 index 0000000..5433bb2 Binary files /dev/null and b/.doctrees/reference/param/generated/param.edit_constant.doctree differ diff --git a/.doctrees/reference/param/generated/param.get_soft_bounds.doctree b/.doctrees/reference/param/generated/param.get_soft_bounds.doctree new file mode 100644 index 0000000..742fb44 Binary files /dev/null and b/.doctrees/reference/param/generated/param.get_soft_bounds.doctree differ diff --git a/.doctrees/reference/param/generated/param.guess_bounds.doctree b/.doctrees/reference/param/generated/param.guess_bounds.doctree new file mode 100644 index 0000000..0f11d03 Binary files /dev/null and b/.doctrees/reference/param/generated/param.guess_bounds.doctree differ diff --git a/.doctrees/reference/param/generated/param.guess_param_types.doctree b/.doctrees/reference/param/generated/param.guess_param_types.doctree new file mode 100644 index 0000000..e189f52 Binary files /dev/null and b/.doctrees/reference/param/generated/param.guess_param_types.doctree differ diff --git a/.doctrees/reference/param/generated/param.output.doctree b/.doctrees/reference/param/generated/param.output.doctree new file mode 100644 index 0000000..36e3ae7 Binary files /dev/null and b/.doctrees/reference/param/generated/param.output.doctree differ diff --git a/.doctrees/reference/param/generated/param.param_union.doctree b/.doctrees/reference/param/generated/param.param_union.doctree new file mode 100644 index 0000000..e40897c Binary files /dev/null and b/.doctrees/reference/param/generated/param.param_union.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Event.doctree b/.doctrees/reference/param/generated/param.parameterized.Event.doctree new file mode 100644 index 0000000..024cdf8 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Event.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.add_parameter.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.add_parameter.doctree new file mode 100644 index 0000000..8e06553 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.add_parameter.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.debug.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.debug.doctree new file mode 100644 index 0000000..d1301ca Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.debug.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.defaults.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.defaults.doctree new file mode 100644 index 0000000..2d04120 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.defaults.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.deserialize_parameters.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.deserialize_parameters.doctree new file mode 100644 index 0000000..d2be77f Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.deserialize_parameters.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.deserialize_value.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.deserialize_value.doctree new file mode 100644 index 0000000..c435d83 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.deserialize_value.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.force_new_dynamic_value.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.force_new_dynamic_value.doctree new file mode 100644 index 0000000..aec6b75 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.force_new_dynamic_value.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.get_param_values.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.get_param_values.doctree new file mode 100644 index 0000000..c9b34b8 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.get_param_values.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.get_value_generator.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.get_value_generator.doctree new file mode 100644 index 0000000..5d1d421 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.get_value_generator.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.inspect_value.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.inspect_value.doctree new file mode 100644 index 0000000..7f2618d Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.inspect_value.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.log.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.log.doctree new file mode 100644 index 0000000..240b8bf Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.log.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.message.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.message.doctree new file mode 100644 index 0000000..7527596 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.message.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.method_dependencies.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.method_dependencies.doctree new file mode 100644 index 0000000..8fee2f9 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.method_dependencies.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.objects.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.objects.doctree new file mode 100644 index 0000000..440d7a3 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.objects.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.outputs.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.outputs.doctree new file mode 100644 index 0000000..8802c2c Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.outputs.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.params.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.params.doctree new file mode 100644 index 0000000..c58e189 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.params.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.params_depended_on.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.params_depended_on.doctree new file mode 100644 index 0000000..048bede Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.params_depended_on.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.pprint.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.pprint.doctree new file mode 100644 index 0000000..1627eda Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.pprint.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.print_param_defaults.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.print_param_defaults.doctree new file mode 100644 index 0000000..5e4f017 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.print_param_defaults.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.print_param_values.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.print_param_values.doctree new file mode 100644 index 0000000..207a11e Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.print_param_values.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.schema.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.schema.doctree new file mode 100644 index 0000000..1723669 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.schema.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.serialize_parameters.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.serialize_parameters.doctree new file mode 100644 index 0000000..e970265 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.serialize_parameters.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.serialize_value.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.serialize_value.doctree new file mode 100644 index 0000000..52803fd Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.serialize_value.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.set_default.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.set_default.doctree new file mode 100644 index 0000000..58d9172 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.set_default.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.set_dynamic_time_fn.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.set_dynamic_time_fn.doctree new file mode 100644 index 0000000..df5ab64 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.set_dynamic_time_fn.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.set_param.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.set_param.doctree new file mode 100644 index 0000000..edcbb51 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.set_param.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.trigger.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.trigger.doctree new file mode 100644 index 0000000..e3d070f Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.trigger.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.unwatch.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.unwatch.doctree new file mode 100644 index 0000000..6b61283 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.unwatch.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.update.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.update.doctree new file mode 100644 index 0000000..cabcef4 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.update.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.values.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.values.doctree new file mode 100644 index 0000000..67abb0a Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.values.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.verbose.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.verbose.doctree new file mode 100644 index 0000000..e8ece93 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.verbose.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.warning.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.warning.doctree new file mode 100644 index 0000000..7e706bd Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.warning.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.watch.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.watch.doctree new file mode 100644 index 0000000..a48f7af Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.watch.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.watch_values.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.watch_values.doctree new file mode 100644 index 0000000..28121b9 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.watch_values.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Parameters.watchers.doctree b/.doctrees/reference/param/generated/param.parameterized.Parameters.watchers.doctree new file mode 100644 index 0000000..e70450c Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Parameters.watchers.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.Watcher.doctree b/.doctrees/reference/param/generated/param.parameterized.Watcher.doctree new file mode 100644 index 0000000..d75fc2b Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.Watcher.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.batch_call_watchers.doctree b/.doctrees/reference/param/generated/param.parameterized.batch_call_watchers.doctree new file mode 100644 index 0000000..9ecfe5e Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.batch_call_watchers.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.get_logger.doctree b/.doctrees/reference/param/generated/param.parameterized.get_logger.doctree new file mode 100644 index 0000000..0a3b9d1 Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.get_logger.doctree differ diff --git a/.doctrees/reference/param/generated/param.parameterized.logging_level.doctree b/.doctrees/reference/param/generated/param.parameterized.logging_level.doctree new file mode 100644 index 0000000..0e291cd Binary files /dev/null and b/.doctrees/reference/param/generated/param.parameterized.logging_level.doctree differ diff --git a/.doctrees/reference/param/generated/param.script_repr.doctree b/.doctrees/reference/param/generated/param.script_repr.doctree new file mode 100644 index 0000000..ace709b Binary files /dev/null and b/.doctrees/reference/param/generated/param.script_repr.doctree differ diff --git a/.doctrees/reference/param/index.doctree b/.doctrees/reference/param/index.doctree new file mode 100644 index 0000000..3f09871 Binary files /dev/null and b/.doctrees/reference/param/index.doctree differ diff --git a/.doctrees/reference/param/logging.doctree b/.doctrees/reference/param/logging.doctree new file mode 100644 index 0000000..f230466 Binary files /dev/null and b/.doctrees/reference/param/logging.doctree differ diff --git a/.doctrees/reference/param/param_namespace.doctree b/.doctrees/reference/param/param_namespace.doctree new file mode 100644 index 0000000..13f96d8 Binary files /dev/null and b/.doctrees/reference/param/param_namespace.doctree differ diff --git a/.doctrees/reference/param/parameter_helpers.doctree b/.doctrees/reference/param/parameter_helpers.doctree new file mode 100644 index 0000000..c655f9c Binary files /dev/null and b/.doctrees/reference/param/parameter_helpers.doctree differ diff --git a/.doctrees/reference/param/parameterized_helpers.doctree b/.doctrees/reference/param/parameterized_helpers.doctree new file mode 100644 index 0000000..64d92c4 Binary files /dev/null and b/.doctrees/reference/param/parameterized_helpers.doctree differ diff --git a/.doctrees/reference/param/parameterized_objects.doctree b/.doctrees/reference/param/parameterized_objects.doctree new file mode 100644 index 0000000..83ef7b4 Binary files /dev/null and b/.doctrees/reference/param/parameterized_objects.doctree differ diff --git a/.doctrees/reference/param/parameters.doctree b/.doctrees/reference/param/parameters.doctree new file mode 100644 index 0000000..28444f0 Binary files /dev/null and b/.doctrees/reference/param/parameters.doctree differ diff --git a/.doctrees/reference/param/serialization.doctree b/.doctrees/reference/param/serialization.doctree new file mode 100644 index 0000000..b58776d Binary files /dev/null and b/.doctrees/reference/param/serialization.doctree differ diff --git a/.doctrees/releases.doctree b/.doctrees/releases.doctree new file mode 100644 index 0000000..cf3484a Binary files /dev/null and b/.doctrees/releases.doctree differ diff --git a/.doctrees/roadmap.doctree b/.doctrees/roadmap.doctree new file mode 100644 index 0000000..6523bb4 Binary files /dev/null and b/.doctrees/roadmap.doctree differ diff --git a/.doctrees/user_guide/Dependencies_and_Watchers.doctree b/.doctrees/user_guide/Dependencies_and_Watchers.doctree new file mode 100644 index 0000000..a8cabf8 Binary files /dev/null and b/.doctrees/user_guide/Dependencies_and_Watchers.doctree differ diff --git a/.doctrees/user_guide/Dynamic_Parameters.doctree b/.doctrees/user_guide/Dynamic_Parameters.doctree new file mode 100644 index 0000000..239a0c5 Binary files /dev/null and b/.doctrees/user_guide/Dynamic_Parameters.doctree differ diff --git a/.doctrees/user_guide/How_Param_Works.doctree b/.doctrees/user_guide/How_Param_Works.doctree new file mode 100644 index 0000000..2f1ebbe Binary files /dev/null and b/.doctrees/user_guide/How_Param_Works.doctree differ diff --git a/.doctrees/user_guide/Logging_and_Warnings.doctree b/.doctrees/user_guide/Logging_and_Warnings.doctree new file mode 100644 index 0000000..8ae3c96 Binary files /dev/null and b/.doctrees/user_guide/Logging_and_Warnings.doctree differ diff --git a/.doctrees/user_guide/Outputs.doctree b/.doctrees/user_guide/Outputs.doctree new file mode 100644 index 0000000..837d49a Binary files /dev/null and b/.doctrees/user_guide/Outputs.doctree differ diff --git a/.doctrees/user_guide/Parameter_Types.doctree b/.doctrees/user_guide/Parameter_Types.doctree new file mode 100644 index 0000000..40f98e2 Binary files /dev/null and b/.doctrees/user_guide/Parameter_Types.doctree differ diff --git a/.doctrees/user_guide/ParameterizedFunctions.doctree b/.doctrees/user_guide/ParameterizedFunctions.doctree new file mode 100644 index 0000000..ba463ec Binary files /dev/null and b/.doctrees/user_guide/ParameterizedFunctions.doctree differ diff --git a/.doctrees/user_guide/Parameters.doctree b/.doctrees/user_guide/Parameters.doctree new file mode 100644 index 0000000..f7a5513 Binary files /dev/null and b/.doctrees/user_guide/Parameters.doctree differ diff --git a/.doctrees/user_guide/Reactive_Expressions.doctree b/.doctrees/user_guide/Reactive_Expressions.doctree new file mode 100644 index 0000000..78f3bc5 Binary files /dev/null and b/.doctrees/user_guide/Reactive_Expressions.doctree differ diff --git a/.doctrees/user_guide/Serialization_and_Persistence.doctree b/.doctrees/user_guide/Serialization_and_Persistence.doctree new file mode 100644 index 0000000..a6d8eeb Binary files /dev/null and b/.doctrees/user_guide/Serialization_and_Persistence.doctree differ diff --git a/.doctrees/user_guide/Simplifying_Codebases.doctree b/.doctrees/user_guide/Simplifying_Codebases.doctree new file mode 100644 index 0000000..ccff1ef Binary files /dev/null and b/.doctrees/user_guide/Simplifying_Codebases.doctree differ diff --git a/.doctrees/user_guide/index.doctree b/.doctrees/user_guide/index.doctree new file mode 100644 index 0000000..1e37840 Binary files /dev/null and b/.doctrees/user_guide/index.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..a139a9a --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,656 @@ + + + + + + + +
+ + +
+"""
+Callable objects that generate numbers according to different distributions.
+"""
+
+import random
+import operator
+import hashlib
+import struct
+import fractions
+
+from ctypes import c_size_t
+from math import e,pi
+
+import param
+
+
+from param import __version__ # noqa: API import
+
+class TimeAware(param.Parameterized):
+ """
+ Class of objects that have access to a global time function
+ and have the option of using it to generate time-dependent values
+ as necessary.
+
+ In the simplest case, an object could act as a strict function of
+ time, returning the current time transformed according to a fixed
+ equation. Other objects may support locking their results to a
+ timebase, but also work without time. For instance, objects with
+ random state could return a new random value for every call, with
+ no notion of time, or could always return the same value until the
+ global time changes. Subclasses should thus provide an ability to
+ return a time-dependent value, but may not always do so.
+ """
+
+ time_dependent = param.Boolean(default=False, doc="""
+ Whether the given time_fn should be used to constrain the
+ results generated.""")
+
+ time_fn = param.Callable(default=param.Dynamic.time_fn, doc="""
+ Callable used to specify the time that determines the state
+ and return value of the object, if time_dependent=True.""")
+
+
+ def __init__(self, **params):
+ super().__init__(**params)
+ self._check_time_fn()
+
+
+ def _check_time_fn(self, time_instance=False):
+ """
+ If time_fn is the global time function supplied by
+ param.Dynamic.time_fn, make sure Dynamic parameters are using
+ this time function to control their behaviour.
+
+ If time_instance is True, time_fn must be a param.Time instance.
+ """
+ if time_instance and not isinstance(self.time_fn, param.Time):
+ raise AssertionError("%s requires a Time object"
+ % self.__class__.__name__)
+
+ if self.time_dependent:
+ global_timefn = self.time_fn is param.Dynamic.time_fn
+ if global_timefn and not param.Dynamic.time_dependent:
+ raise AssertionError("Cannot use Dynamic.time_fn as"
+ " parameters are ignoring time.")
+
+
+class TimeDependent(TimeAware):
+ """
+ Objects that have access to a time function that determines the
+ output value. As a function of time, this type of object should
+ allow time values to be randomly jumped forwards or backwards,
+ but for a given time point, the results should remain constant.
+
+ The time_fn must be an instance of param.Time, to ensure all the
+ facilities necessary for safely navigating the timeline are
+ available.
+ """
+
+ time_dependent = param.Boolean(default=True, readonly=True, doc="""
+ Read-only parameter that is always True.""")
+
+ def _check_time_fn(self):
+ super()._check_time_fn(time_instance=True)
+
+
+
+[docs]class NumberGenerator(param.Parameterized):
+ """
+ Abstract base class for any object that when called produces a number.
+
+ Primarily provides support for using NumberGenerators in simple
+ arithmetic expressions, such as abs((x+y)/z), where x,y,z are
+ NumberGenerators or numbers.
+ """
+
+ def __call__(self):
+ raise NotImplementedError
+
+ # Could define any of Python's operators here, esp. if they have operator or ufunc equivalents
+ def __add__ (self,operand): return BinaryOperator(self,operand,operator.add)
+ def __sub__ (self,operand): return BinaryOperator(self,operand,operator.sub)
+ def __mul__ (self,operand): return BinaryOperator(self,operand,operator.mul)
+ def __mod__ (self,operand): return BinaryOperator(self,operand,operator.mod)
+ def __pow__ (self,operand): return BinaryOperator(self,operand,operator.pow)
+ def __div__ (self,operand): return BinaryOperator(self,operand,operator.div)
+ def __truediv__ (self,operand): return BinaryOperator(self,operand,operator.truediv)
+ def __floordiv__ (self,operand): return BinaryOperator(self,operand,operator.floordiv)
+
+ def __radd__ (self,operand): return BinaryOperator(self,operand,operator.add,True)
+ def __rsub__ (self,operand): return BinaryOperator(self,operand,operator.sub,True)
+ def __rmul__ (self,operand): return BinaryOperator(self,operand,operator.mul,True)
+ def __rmod__ (self,operand): return BinaryOperator(self,operand,operator.mod,True)
+ def __rpow__ (self,operand): return BinaryOperator(self,operand,operator.pow,True)
+ def __rdiv__ (self,operand): return BinaryOperator(self,operand,operator.div,True)
+ def __rtruediv__ (self,operand): return BinaryOperator(self,operand,operator.truediv,True)
+ def __rfloordiv__(self,operand): return BinaryOperator(self,operand,operator.floordiv,True)
+
+ def __neg__ (self): return UnaryOperator(self,operator.neg)
+ def __pos__ (self): return UnaryOperator(self,operator.pos)
+ def __abs__ (self): return UnaryOperator(self,operator.abs)
+
+
+operator_symbols = {
+ operator.add:'+',
+ operator.sub:'-',
+ operator.mul:'*',
+ operator.mod:'%',
+ operator.pow:'**',
+ operator.truediv:'/',
+ operator.floordiv:'//',
+ operator.neg:'-',
+ operator.pos:'+',
+ operator.abs:'abs',
+}
+
+def pprint(x, *args, **kwargs):
+ "Pretty-print the provided item, translating operators to their symbols"
+ return x.pprint(*args, **kwargs) if hasattr(x,'pprint') else operator_symbols.get(x, repr(x))
+
+
+[docs]class BinaryOperator(NumberGenerator):
+ """Applies any binary operator to NumberGenerators or numbers to yield a NumberGenerator."""
+
+ def __init__(self,lhs,rhs,operator,reverse=False,**args):
+ """
+ Accepts two NumberGenerator operands, an operator, and
+ optional arguments to be provided to the operator when calling
+ it on the two operands.
+ """
+ # Note that it's currently not possible to set
+ # parameters in the superclass when creating an instance,
+ # because **args is used by this class itself.
+ super().__init__()
+
+ if reverse:
+ self.lhs=rhs
+ self.rhs=lhs
+ else:
+ self.lhs=lhs
+ self.rhs=rhs
+ self.operator=operator
+ self.args=args
+
+ def __call__(self):
+ return self.operator(self.lhs() if callable(self.lhs) else self.lhs,
+ self.rhs() if callable(self.rhs) else self.rhs, **self.args)
+
+ def pprint(self, *args, **kwargs):
+ return (pprint(self.lhs, *args, **kwargs) +
+ pprint(self.operator, *args, **kwargs) +
+ pprint(self.rhs, *args, **kwargs))
+
+
+[docs]class UnaryOperator(NumberGenerator):
+ """Applies any unary operator to a NumberGenerator to yield another NumberGenerator."""
+
+ def __init__(self,operand,operator,**args):
+ """
+ Accepts a NumberGenerator operand, an operator, and
+ optional arguments to be provided to the operator when calling
+ it on the operand.
+ """
+ # Note that it's currently not possible to set
+ # parameters in the superclass when creating an instance,
+ # because **args is used by this class itself.
+ super().__init__()
+
+ self.operand=operand
+ self.operator=operator
+ self.args=args
+
+ def __call__(self):
+ return self.operator(self.operand(),**self.args)
+
+ def pprint(self, *args, **kwargs):
+ return (pprint(self.operator, *args, **kwargs) + '(' +
+ pprint(self.operand, *args, **kwargs) + ')')
+
+
+class Hash:
+ """
+ A platform- and architecture-independent hash function (unlike
+ Python's inbuilt hash function) for use with an ordered collection
+ of rationals or integers.
+
+ The supplied name sets the initial hash state. The output from
+ each call is a 32-bit integer to ensure the value is a regular
+ Python integer (and not a Python long) on both 32-bit and 64-bit
+ platforms. This can be important to seed Numpy's random number
+ generator safely (a bad Numpy bug!).
+
+ The number of inputs (integer or rational numbers) to be supplied
+ for __call__ must be specified in the constructor and must stay
+ constant across calls.
+ """
+ def __init__(self, name, input_count):
+ self.name = name
+ self.input_count = input_count
+ self._digest = hashlib.md5()
+ self._digest.update(name.encode())
+ self._hash_struct = struct.Struct( "!" +" ".join(["I"] * (input_count * 2)))
+
+
+ def _rational(self, val):
+ """Convert the given value to a rational, if necessary."""
+
+ I32 = 4294967296 # Maximum 32 bit unsigned int (i.e. 'I') value
+ if isinstance(val, int):
+ numer, denom = val, 1
+ elif isinstance(val, fractions.Fraction):
+ numer, denom = val.numerator, val.denominator
+ elif hasattr(val, 'numer'):
+ (numer, denom) = (int(val.numer()), int(val.denom()))
+ else:
+ param.main.param.log(param.WARNING, "Casting type '%s' to Fraction.fraction"
+ % type(val).__name__)
+ frac = fractions.Fraction(str(val))
+ numer, denom = frac.numerator, frac.denominator
+ return numer % I32, denom % I32
+
+
+ def __getstate__(self):
+ """
+ Avoid Hashlib.md5 TypeError in deepcopy (hashlib issue)
+ """
+ d = self.__dict__.copy()
+ d.pop('_digest')
+ d.pop('_hash_struct')
+ return d
+
+
+ def __setstate__(self, d):
+ self._digest = hashlib.md5()
+ name, input_count = d['name'], d['input_count']
+ self._digest.update(name.encode())
+ self._hash_struct = struct.Struct( "!" +" ".join(["I"] * (input_count * 2)))
+ self.__dict__.update(d)
+
+
+ def __call__(self, *vals):
+ """
+ Given integer or rational inputs, generate a cross-platform,
+ architecture-independent 32-bit integer hash.
+ """
+ # Convert inputs to (numer, denom) pairs with integers
+ # becoming (int, 1) pairs to match gmpy.mpqs for int values.
+ pairs = [self._rational(val) for val in vals]
+ # Unpack pairs and fill struct with ints to update md5 hash
+ ints = [el for pair in pairs for el in pair]
+ digest = self._digest.copy()
+ digest.update(self._hash_struct.pack(*ints))
+ # Convert from hex string to 32 bit int
+ return int(digest.hexdigest()[:7], 16)
+
+
+
+class TimeAwareRandomState(TimeAware):
+ """
+ Generic base class to enable time-dependent random
+ streams. Although this class is the basis of all random numbergen
+ classes, it is designed to be useful whenever time-dependent
+ randomness is needed using param's notion of time. For instance,
+ this class is used by the imagen package to define time-dependent,
+ random distributions over 2D arrays.
+
+ For generality, this class may use either the Random class from
+ Python's random module or numpy.random.RandomState. Either of
+ these random state objects may be used to generate numbers from
+ any of several different random distributions (e.g. uniform,
+ Gaussian). The latter offers the ability to generate
+ multi-dimensional random arrays and more random distributions but
+ requires numpy as a dependency.
+
+ If declared time_dependent, the random state is fully determined
+ by a hash value per call. The hash is initialized once with the
+ object name and then per call using a tuple consisting of the time
+ (via time_fn) and the global param.random_seed. As a consequence,
+ for a given name and fixed value of param.random_seed, the random
+ values generated will be a fixed function of time.
+
+ If the object name has not been set and time_dependent is True, a
+ message is generated warning that the default object name is
+ dependent on the order of instantiation. To ensure that the
+ random number stream will remain constant even if other objects
+ are added or reordered in your file, supply a unique name
+ explicitly when you construct the RandomDistribution object.
+ """
+
+ # Historically, the default random state was seeded with the tuple
+ # (500, 500). The CPython implementation implicitly formed an unsigned
+ # integer seed using the hash of the tuple as in the expression below. Note
+ # that the resulting integer, and therefore the default initial random
+ # state, varies across CPython versions (as the hash algorithm has changed)
+ # and also between 32-bit and 64-bit interpreters.
+ #
+ # Seeding based on hashing is deprecated since Python 3.9 and removed in
+ # Python 3.11; we explicitly continue the historical behavior for the time
+ # being.
+ random_generator = param.Parameter(
+ default=random.Random(c_size_t(hash((500,500))).value), doc=
+ """
+ Random state used by the object. This may be an instance
+ of random.Random from the Python standard library or an
+ instance of numpy.random.RandomState.
+
+ This random state may be exclusively owned by the object or
+ may be shared by all instance of the same class. It is always
+ possible to give an object its own unique random state by
+ setting this parameter with a new random state instance.
+ """)
+
+ __abstract = True
+
+ def _initialize_random_state(self, seed=None, shared=True, name=None):
+ """
+ Initialization method to be called in the constructor of
+ subclasses to initialize the random state correctly.
+
+ If seed is None, there is no control over the random stream
+ (no reproducibility of the stream).
+
+ If shared is True (and not time-dependent), the random state
+ is shared across all objects of the given class. This can be
+ overridden per object by creating new random state to assign
+ to the random_generator parameter.
+ """
+ if seed is None: # Equivalent to an uncontrolled seed.
+ seed = random.Random().randint(0, 1000000)
+ suffix = ''
+ else:
+ suffix = str(seed)
+
+ # If time_dependent, independent state required: otherwise
+ # time-dependent seeding (via hash) will affect shared
+ # state. Note that if all objects have time_dependent=True
+ # shared random state is safe and more memory efficient.
+ if self.time_dependent or not shared:
+ self.random_generator = type(self.random_generator)(seed)
+
+ # Seed appropriately (if not shared)
+ if not shared:
+ self.random_generator.seed(seed)
+
+ if name is None:
+ self._verify_constrained_hash()
+
+ hash_name = name if name else self.name
+ if not shared: hash_name += suffix
+ self._hashfn = Hash(hash_name, input_count=2)
+
+ if self.time_dependent:
+ self._hash_and_seed()
+
+
+ def _verify_constrained_hash(self):
+ """
+ Warn if the object name is not explicitly set.
+ """
+ changed_params = self.param.values(onlychanged=True)
+ if self.time_dependent and ('name' not in changed_params):
+ self.param.log(param.WARNING, "Default object name used to set the seed: "
+ "random values conditional on object instantiation order.")
+
+ def _hash_and_seed(self):
+ """
+ To be called between blocks of random number generation. A
+ 'block' can be an unbounded sequence of random numbers so long
+ as the time value (as returned by time_fn) is guaranteed not
+ to change within the block. If this condition holds, each
+ block of random numbers is time-dependent.
+
+ Note: param.random_seed is assumed to be integer or rational.
+ """
+ hashval = self._hashfn(self.time_fn(), param.random_seed)
+ self.random_generator.seed(hashval)
+
+
+
+[docs]class RandomDistribution(NumberGenerator, TimeAwareRandomState):
+ """
+ The base class for all Numbergenerators using random state.
+
+ Numbergen provides a hierarchy of classes to make it easier to use
+ the random distributions made available in Python's random module,
+ where each class is tied to a particular random distribution.
+
+ RandomDistributions support setting parameters on creation rather
+ than passing them each call, and allow pickling to work properly.
+ Code that uses these classes will be independent of how many
+ parameters are used by the underlying distribution, and can simply
+ treat them as a generic source of random numbers.
+
+ RandomDistributions are examples of TimeAwareRandomState, and thus
+ can be locked to a global time if desired. By default,
+ time_dependent=False, and so a new random value will be generated
+ each time these objects are called. If you have a global time
+ function, you can set time_dependent=True, so that the random
+ values will instead be constant at any given time, changing only
+ when the time changes. Using time_dependent values can help you
+ obtain fully reproducible streams of random numbers, even if you
+ e.g. move time forwards and backwards for testing.
+
+ Note: Each RandomDistribution object has independent random state.
+ """
+
+ seed = param.Integer(default=None, allow_None=True, doc="""
+ Sets the seed of the random number generator and can be used to
+ randomize time dependent streams.
+
+ If seed is None, there is no control over the random stream
+ (i.e. no reproducibility of the stream).""")
+
+ __abstract = True
+
+ def __init__(self,**params):
+ """
+ Initialize a new Random() instance and store the supplied
+ positional and keyword arguments.
+
+ If seed=X is specified, sets the Random() instance's seed.
+ Otherwise, calls creates an unseeded Random instance which is
+ likely to result in a state very different from any just used.
+ """
+ super().__init__(**params)
+ self._initialize_random_state(seed=self.seed, shared=False)
+
+ def __call__(self):
+ if self.time_dependent:
+ self._hash_and_seed()
+
+
+[docs]class UniformRandom(RandomDistribution):
+ """
+ Specified with lbound and ubound; when called, return a random
+ number in the range [lbound, ubound).
+
+ See the random module for further details.
+ """
+
+ lbound = param.Number(default=0.0,doc="Inclusive lower bound.")
+
+ ubound = param.Number(default=1.0,doc="Exclusive upper bound.")
+
+
+ def __call__(self):
+ super().__call__()
+ return self.random_generator.uniform(self.lbound,self.ubound)
+
+
+
+[docs]class UniformRandomOffset(RandomDistribution):
+ """
+ Identical to UniformRandom, but specified by mean and range.
+ When called, return a random number in the range
+ [mean - range/2, mean + range/2).
+
+ See the random module for further details.
+ """
+
+ mean = param.Number(default=0.0, doc="""Mean value""")
+
+ range = param.Number(default=1.0, bounds=(0.0,None), doc="""
+ Difference of maximum and minimum value""")
+
+
+ def __call__(self):
+ super().__call__()
+ return self.random_generator.uniform(
+ self.mean - self.range / 2.0,
+ self.mean + self.range / 2.0)
+
+
+
+[docs]class UniformRandomInt(RandomDistribution):
+ """
+ Specified with lbound and ubound; when called, return a random
+ number in the inclusive range [lbound, ubound].
+
+ See the randint function in the random module for further details.
+ """
+
+ lbound = param.Number(default=0,doc="Inclusive lower bound.")
+ ubound = param.Number(default=1000,doc="Inclusive upper bound.")
+
+
+ def __call__(self):
+ super().__call__()
+ x = self.random_generator.randint(self.lbound,self.ubound)
+ return x
+
+
+
+[docs]class Choice(RandomDistribution):
+ """
+ Return a random element from the specified list of choices.
+
+ Accepts items of any type, though they are typically numbers.
+ See the choice() function in the random module for further details.
+ """
+
+ choices = param.List(default=[0,1],
+ doc="List of items from which to select.")
+
+
+ def __call__(self):
+ super().__call__()
+ return self.random_generator.choice(self.choices)
+
+
+
+[docs]class NormalRandom(RandomDistribution):
+ """
+ Normally distributed (Gaussian) random number.
+
+ Specified with mean mu and standard deviation sigma.
+ See the random module for further details.
+ """
+
+ mu = param.Number(default=0.0,doc="Mean value.")
+
+ sigma = param.Number(default=1.0,bounds=(0.0,None),doc="Standard deviation.")
+
+
+ def __call__(self):
+ super().__call__()
+ return self.random_generator.normalvariate(self.mu,self.sigma)
+
+
+
+[docs]class VonMisesRandom(RandomDistribution):
+ """
+ Circularly normal distributed random number.
+
+ If kappa is zero, this distribution reduces to a uniform random
+ angle over the range 0 to 2*pi. Otherwise, it is concentrated to
+ a greater or lesser degree (determined by kappa) around the mean
+ mu. For large kappa (narrow peaks), this distribution approaches
+ the Gaussian (normal) distribution with variance 1/kappa. See the
+ random module for further details.
+ """
+
+ mu = param.Number(default=0.0,softbounds=(0.0,2*pi),doc="""
+ Mean value, typically in the range 0 to 2*pi.""")
+
+ kappa = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,50.0),doc="""
+ Concentration (inverse variance).""")
+
+
+ def __call__(self):
+ super().__call__()
+ return self.random_generator.vonmisesvariate(self.mu,self.kappa)
+
+
+
+
+[docs]class ScaledTime(NumberGenerator, TimeDependent):
+ """
+ The current time multiplied by some conversion factor.
+ """
+
+ factor = param.Number(default=1.0, doc="""
+ The factor to be multiplied by the current time value.""")
+
+
+ def __call__(self):
+ return float(self.time_fn() * self.factor)
+
+
+
+[docs]class BoxCar(NumberGenerator, TimeDependent):
+ """
+ The boxcar function over the specified time interval. The bounds
+ are exclusive: zero is returned at the onset time and at the
+ offset (onset+duration).
+
+ If duration is None, then this reduces to a step function around the
+ onset value with no offset.
+
+ See http://en.wikipedia.org/wiki/Boxcar_function
+ """
+
+ onset = param.Number(0.0, doc="Time of onset.")
+
+ duration = param.Number(None, allow_None=True, bounds=(0.0,None), doc="""
+ Duration of step value.""")
+
+
+ def __call__(self):
+ if self.time_fn() <= self.onset:
+ return 0.0
+ elif (self.duration is not None) and (self.time_fn() > self.onset + self.duration):
+ return 0.0
+ else:
+ return 1.0
+
+
+
+[docs]class SquareWave(NumberGenerator, TimeDependent):
+ """
+ Generate a square wave with 'on' periods returning 1.0 and
+ 'off'periods returning 0.0 of specified duration(s). By default
+ the portion of time spent in the high state matches the time spent
+ in the low state (a duty cycle of 50%), but the duty cycle can be
+ controlled if desired.
+
+ The 'on' state begins after a time specified by the 'onset'
+ parameter. The onset duration supplied must be less than the off
+ duration.
+ """
+
+ onset = param.Number(0.0, doc="""Time of onset of the first 'on'
+ state relative to time 0. Must be set to a value less than the
+ 'off_duration' parameter.""")
+
+ duration = param.Number(1.0, allow_None=False, bounds=(0.0,None), doc="""
+ Duration of the 'on' state during which a value of 1.0 is
+ returned.""")
+
+ off_duration = param.Number(default=None, allow_None=True,
+ bounds=(0.0,None), doc="""
+ Duration of the 'off' value state during which a value of 0.0
+ is returned. By default, this duration matches the value of
+ the 'duration' parameter.""")
+
+
+ def __init__(self, **params):
+ super().__init__(**params)
+
+ if self.off_duration is None:
+ self.off_duration = self.duration
+
+ if self.onset > self.off_duration:
+ raise AssertionError("Onset value needs to be less than %s" % self.onset)
+
+
+ def __call__(self):
+ phase_offset = (self.time_fn() - self.onset) % (self.duration + self.off_duration)
+ if phase_offset < self.duration:
+ return 1.0
+ else:
+ return 0.0
+
+
+
+[docs]class ExponentialDecay(NumberGenerator, TimeDependent):
+ """
+ Function object that provides a value that decays according to an
+ exponential function, based on a given time function.
+
+ Returns starting_value*base^(-time/time_constant).
+
+ See http://en.wikipedia.org/wiki/Exponential_decay.
+ """
+
+ starting_value = param.Number(1.0, doc="Value used for time zero.")
+
+ ending_value = param.Number(0.0, doc="Value used for time infinity.")
+
+ time_constant = param.Number(10000,doc="""
+ Time scale for the exponential; large values give slow decay.""")
+
+ base = param.Number(e, doc="""
+ Base of the exponent; the default yields starting_value*exp(-t/time_constant).
+ Another popular choice of base is 2, which allows the
+ time_constant to be interpreted as a half-life.""")
+
+
+ def __call__(self):
+ Vi = self.starting_value
+ Vm = self.ending_value
+ exp = -1.0*float(self.time_fn())/float(self.time_constant)
+ return Vm + (Vi - Vm) * self.base**exp
+
+
+
+[docs]class TimeSampledFn(NumberGenerator, TimeDependent):
+ """
+ Samples the values supplied by a time_dependent callable at
+ regular intervals of duration 'period', with the sampled value
+ held constant within each interval.
+ """
+
+ period = param.Number(default=1.0, bounds=(0.0,None),
+ inclusive_bounds=(False,True), softbounds=(0.0,5.0), doc="""
+ The periodicity with which the values of fn are sampled.""")
+
+ offset = param.Number(default=0.0, bounds=(0.0,None),
+ softbounds=(0.0,5.0), doc="""
+ The offset from time 0.0 at which the first sample will be drawn.
+ Must be less than the value of period.""")
+
+ fn = param.Callable(doc="""
+ The time-dependent function used to generate the sampled values.""")
+
+
+ def __init__(self, **params):
+ super().__init__(**params)
+
+ if not getattr(self.fn,'time_dependent', False):
+ raise Exception("The function 'fn' needs to be time dependent.")
+
+ if self.time_fn != self.fn.time_fn:
+ raise Exception("Objects do not share the same time_fn")
+
+ if self.offset >= self.period:
+ raise Exception("The onset value must be less than the period.")
+
+
+ def __call__(self):
+ current_time = self.time_fn()
+ current_time += self.offset
+ difference = current_time % self.period
+ with self.time_fn as t:
+ t(current_time - difference - self.offset)
+ value = self.fn()
+ return value
+
+
+
+[docs]class BoundedNumber(NumberGenerator):
+ """
+ Function object that silently enforces numeric bounds on values
+ returned by a callable object.
+ """
+
+ generator = param.Callable(None, doc="Object to call to generate values.")
+
+ bounds = param.Parameter((None,None), doc="""
+ Legal range for the value returned, as a pair.
+
+ The default bounds are (None,None), meaning there are actually
+ no bounds. One or both bounds can be set by specifying a
+ value. For instance, bounds=(None,10) means there is no lower
+ bound, and an upper bound of 10.""")
+
+
+ def __call__(self):
+ val = self.generator()
+ min_, max_ = self.bounds
+ if min_ is not None and val < min_: return min_
+ elif max_ is not None and val > max_: return max_
+ else: return val
+
+
+_public = list({_k for _k,_v in locals().items() if isinstance(_v,type) and issubclass(_v,NumberGenerator)})
+__all__ = _public
+
+"""
+Parameters are a kind of class attribute allowing special behavior,
+including dynamically generated parameter values, documentation
+strings, constant and read-only parameters, and type or range checking
+at assignment time.
+
+Potentially useful for any large Python program that needs
+user-modifiable object attributes; see the Parameter and Parameterized
+classes for more information. If you do not want to add a dependency
+on external code by importing from a separately installed param
+package, you can simply save this file as param.py and copy it and
+parameterized.py directly into your own package.
+
+This file contains subclasses of Parameter, implementing specific
+parameter types (e.g. Number), and also imports the definition of
+Parameters and Parameterized classes.
+"""
+
+import os.path
+import sys
+import copy
+import glob
+import re
+import datetime as dt
+import collections
+import pathlib
+import typing
+import warnings
+
+from collections import OrderedDict
+from contextlib import contextmanager
+from numbers import Real
+
+from . import version # noqa: api import
+
+from .depends import depends # noqa: api import
+from .parameterized import (
+ Parameterized, Parameter, String, ParameterizedFunction, ParamOverrides,
+ Undefined, descendents, get_logger, instance_descriptor, dt_types,
+ _int_types
+)
+from .parameterized import (batch_watch, output, script_repr, # noqa: api import
+ discard_events, edit_constant, instance_descriptor)
+from .parameterized import shared_parameters # noqa: api import
+from .parameterized import logging_level # noqa: api import
+from .parameterized import DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL # noqa: api import
+from .parameterized import _identity_hook
+from ._utils import (
+ ParamDeprecationWarning as _ParamDeprecationWarning,
+ _deprecate_positional_args,
+ _deprecated,
+ _dict_update,
+ _validate_error_prefix,
+)
+
+# Define '__version__'
+try:
+ # If setuptools_scm is installed (e.g. in a development environment with
+ # an editable install), then use it to determine the version dynamically.
+ from setuptools_scm import get_version
+
+ # This will fail with LookupError if the package is not installed in
+ # editable mode or if Git is not installed.
+ __version__ = get_version(root="..", relative_to=__file__)
+except (ImportError, LookupError):
+ # As a fallback, use the version that is hard-coded in the file.
+ try:
+ from ._version import __version__
+ except ModuleNotFoundError:
+ # The user is probably trying to run this without having installed
+ # the package.
+ __version__ = "0.0.0+unknown"
+
+#: Top-level object to allow messaging not tied to a particular
+#: Parameterized object, as in 'param.main.warning("Invalid option")'.
+main=Parameterized(name="main")
+
+
+# A global random seed (integer or rational) available for controlling
+# the behaviour of Parameterized objects with random state.
+random_seed = 42
+
+
+def _produce_value(value_obj):
+ """
+ A helper function that produces an actual parameter from a stored
+ object: if the object is callable, call it, otherwise return the
+ object.
+ """
+ if callable(value_obj):
+ return value_obj()
+ else:
+ return value_obj
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def produce_value(value_obj):
+ """
+ A helper function that produces an actual parameter from a stored
+ object: if the object is callable, call it, otherwise return the
+ object.
+
+ .. deprecated:: 2.0.0
+ """
+ return _produce_value(value_obj)
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def as_unicode(obj):
+ """
+ Safely casts any object to unicode including regular string
+ (i.e. bytes) types in python 2.
+
+ .. deprecated:: 2.0.0
+ """
+ return str(obj)
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def is_ordered_dict(d):
+ """
+ Predicate checking for ordered dictionaries. OrderedDict is always
+ ordered, and vanilla Python dictionaries are ordered for Python 3.6+
+
+ .. deprecated:: 2.0.0
+ """
+ py3_ordered_dicts = (sys.version_info.major == 3) and (sys.version_info.minor >= 6)
+ vanilla_odicts = (sys.version_info.major > 3) or py3_ordered_dicts
+ return isinstance(d, (OrderedDict)) or (vanilla_odicts and isinstance(d, dict))
+
+
+def _hashable(x):
+ """
+ Return a hashable version of the given object x, with lists and
+ dictionaries converted to tuples. Allows mutable objects to be
+ used as a lookup key in cases where the object has not actually
+ been mutated. Lookup will fail (appropriately) in cases where some
+ part of the object has changed. Does not (currently) recursively
+ replace mutable subobjects.
+ """
+ if isinstance(x, collections.abc.MutableSequence):
+ return tuple(x)
+ elif isinstance(x, collections.abc.MutableMapping):
+ return tuple([(k,v) for k,v in x.items()])
+ else:
+ return x
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def hashable(x):
+ """
+ Return a hashable version of the given object x, with lists and
+ dictionaries converted to tuples. Allows mutable objects to be
+ used as a lookup key in cases where the object has not actually
+ been mutated. Lookup will fail (appropriately) in cases where some
+ part of the object has changed. Does not (currently) recursively
+ replace mutable subobjects.
+
+ .. deprecated:: 2.0.0
+ """
+ return _hashable(x)
+
+
+def _named_objs(objlist, namesdict=None):
+ """
+ Given a list of objects, returns a dictionary mapping from
+ string name for the object to the object itself. Accepts
+ an optional name,obj dictionary, which will override any other
+ name if that item is present in the dictionary.
+ """
+ objs = OrderedDict()
+
+ objtoname = {}
+ unhashables = []
+ if namesdict is not None:
+ for k, v in namesdict.items():
+ try:
+ objtoname[_hashable(v)] = k
+ except TypeError:
+ unhashables.append((k, v))
+
+ for obj in objlist:
+ if objtoname and _hashable(obj) in objtoname:
+ k = objtoname[_hashable(obj)]
+ elif any(obj is v for (_, v) in unhashables):
+ k = [k for (k, v) in unhashables if v is obj][0]
+ elif hasattr(obj, "name"):
+ k = obj.name
+ elif hasattr(obj, '__name__'):
+ k = obj.__name__
+ else:
+ k = str(obj)
+ objs[k] = obj
+ return objs
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def named_objs(objlist, namesdict=None):
+ """
+ Given a list of objects, returns a dictionary mapping from
+ string name for the object to the object itself. Accepts
+ an optional name,obj dictionary, which will override any other
+ name if that item is present in the dictionary.
+
+ .. deprecated:: 2.0.0
+ """
+ return _named_objs(objlist, namesdict=namesdict)
+
+
+[docs]def param_union(*parameterizeds, warn=True):
+ """
+ Given a set of Parameterized objects, returns a dictionary
+ with the union of all param name,value pairs across them.
+
+ Parameters
+ ----------
+ warn : bool, optional
+ Wether to warn if the same parameter have been given multiple values,
+ otherwise use the last value, by default True
+
+ Returns
+ -------
+ dict
+ Union of all param name,value pairs
+ """
+ d = {}
+ for o in parameterizeds:
+ for k in o.param:
+ if k != 'name':
+ if k in d and warn:
+ get_logger().warning(f"overwriting parameter {k}")
+ d[k] = getattr(o, k)
+ return d
+
+
+[docs]def guess_param_types(**kwargs):
+ """
+ Given a set of keyword literals, promote to the appropriate
+ parameter type based on some simple heuristics.
+ """
+ params = {}
+ for k, v in kwargs.items():
+ kws = dict(default=v, constant=True)
+ if isinstance(v, Parameter):
+ params[k] = v
+ elif isinstance(v, dt_types):
+ params[k] = Date(**kws)
+ elif isinstance(v, bool):
+ params[k] = Boolean(**kws)
+ elif isinstance(v, int):
+ params[k] = Integer(**kws)
+ elif isinstance(v, float):
+ params[k] = Number(**kws)
+ elif isinstance(v, str):
+ params[k] = String(**kws)
+ elif isinstance(v, dict):
+ params[k] = Dict(**kws)
+ elif isinstance(v, tuple):
+ if all(_is_number(el) for el in v):
+ params[k] = NumericTuple(**kws)
+ elif all(isinstance(el, dt_types) for el in v) and len(v)==2:
+ params[k] = DateRange(**kws)
+ else:
+ params[k] = Tuple(**kws)
+ elif isinstance(v, list):
+ params[k] = List(**kws)
+ else:
+ if 'numpy' in sys.modules:
+ from numpy import ndarray
+ if isinstance(v, ndarray):
+ params[k] = Array(**kws)
+ continue
+ if 'pandas' in sys.modules:
+ from pandas import (
+ DataFrame as pdDFrame, Series as pdSeries
+ )
+ if isinstance(v, pdDFrame):
+ params[k] = DataFrame(**kws)
+ continue
+ elif isinstance(v, pdSeries):
+ params[k] = Series(**kws)
+ continue
+ params[k] = Parameter(**kws)
+
+ return params
+
+
+def parameterized_class(name, params, bases=Parameterized):
+ """
+ Dynamically create a parameterized class with the given name and the
+ supplied parameters, inheriting from the specified base(s).
+ """
+ if not (isinstance(bases, list) or isinstance(bases, tuple)):
+ bases=[bases]
+ return type(name, tuple(bases), params)
+
+
+[docs]def guess_bounds(params, **overrides):
+ """
+ Given a dictionary of Parameter instances, return a corresponding
+ set of copies with the bounds appropriately set.
+
+
+ If given a set of override keywords, use those numeric tuple bounds.
+ """
+ guessed = {}
+ for name, p in params.items():
+ new_param = copy.copy(p)
+ if isinstance(p, (Integer, Number)):
+ if name in overrides:
+ minv,maxv = overrides[name]
+ else:
+ minv, maxv, _ = _get_min_max_value(None, None, value=p.default)
+ new_param.bounds = (minv, maxv)
+ guessed[name] = new_param
+ return guessed
+
+
+def _get_min_max_value(min, max, value=None, step=None):
+ """Return min, max, value given input values with possible None."""
+ # Either min and max need to be given, or value needs to be given
+ if value is None:
+ if min is None or max is None:
+ raise ValueError(
+ f'unable to infer range, value from: ({min}, {max}, {value})'
+ )
+ diff = max - min
+ value = min + (diff / 2)
+ # Ensure that value has the same type as diff
+ if not isinstance(value, type(diff)):
+ value = min + (diff // 2)
+ else: # value is not None
+ if not isinstance(value, Real):
+ raise TypeError('expected a real number, got: %r' % value)
+ # Infer min/max from value
+ if value == 0:
+ # This gives (0, 1) of the correct type
+ vrange = (value, value + 1)
+ elif value > 0:
+ vrange = (-value, 3*value)
+ else:
+ vrange = (3*value, -value)
+ if min is None:
+ min = vrange[0]
+ if max is None:
+ max = vrange[1]
+ if step is not None:
+ # ensure value is on a step
+ tick = int((value - min) / step)
+ value = min + tick * step
+ if not min <= value <= max:
+ raise ValueError(f'value must be between min and max (min={min}, value={value}, max={max})')
+ return min, max, value
+
+
+def _deserialize_from_path(ext_to_routine, path, type_name):
+ """
+ Call deserialization routine with path according to extension.
+ ext_to_routine should be a dictionary mapping each supported
+ file extension to a corresponding loading function.
+ """
+ if not os.path.isfile(path):
+ raise FileNotFoundError(
+ "Could not parse file '{}' as {}: does not exist or is not a file"
+ "".format(path, type_name))
+ root, ext = os.path.splitext(path)
+ if ext in {'.gz', '.bz2', '.xz', '.zip'}:
+ # A compressed type. We'll assume the routines can handle such extensions
+ # transparently (if not, we'll fail later)
+ ext = os.path.splitext(root)[1]
+ # FIXME(sdrobert): try...except block below with "raise from" might be a good idea
+ # once py2.7 support is removed. Provides error + fact that failure occurred in
+ # deserialization
+ if ext in ext_to_routine:
+ return ext_to_routine[ext](path)
+ raise ValueError(
+ "Could not parse file '{}' as {}: no deserialization method for files with "
+ "'{}' extension. Supported extensions: {}"
+ "".format(path, type_name, ext, ', '.join(sorted(ext_to_routine))))
+
+
+class Infinity:
+ """
+ An instance of this class represents an infinite value. Unlike
+ Python's float('inf') value, this object can be safely compared
+ with gmpy numeric types across different gmpy versions.
+
+ All operators on Infinity() return Infinity(), apart from the
+ comparison and equality operators. Equality works by checking
+ whether the two objects are both instances of this class.
+ """
+
+ def __eq__ (self,other): return isinstance(other,self.__class__)
+ def __ne__ (self,other): return not self==other
+ def __lt__ (self,other): return False
+ def __le__ (self,other): return False
+ def __gt__ (self,other): return True
+ def __ge__ (self,other): return True
+ def __add__ (self,other): return self
+ def __radd__(self,other): return self
+ def __ladd__(self,other): return self
+ def __sub__ (self,other): return self
+ def __iadd_ (self,other): return self
+ def __isub__(self,other): return self
+ def __repr__(self): return "Infinity()"
+ def __str__ (self): return repr(self)
+
+
+
+class Time(Parameterized):
+ """
+ A callable object returning a number for the current time.
+
+ Here 'time' is an abstract concept that can be interpreted in any
+ useful way. For instance, in a simulation, it would be the
+ current simulation time, while in a turn-taking game it could be
+ the number of moves so far. The key intended usage is to allow
+ independent Parameterized objects with Dynamic parameters to
+ remain consistent with a global reference.
+
+ The time datatype (time_type) is configurable, but should
+ typically be an exact numeric type like an integer or a rational,
+ so that small floating-point errors do not accumulate as time is
+ incremented repeatedly.
+
+ When used as a context manager using the 'with' statement
+ (implemented by the __enter__ and __exit__ special methods), entry
+ into a context pushes the state of the Time object, allowing the
+ effect of changes to the time value to be explored by setting,
+ incrementing or decrementing time as desired. This allows the
+ state of time-dependent objects to be modified temporarily as a
+ function of time, within the context's block. For instance, you
+ could use the context manager to "see into the future" to collect
+ data over multiple times, without affecting the global time state
+ once exiting the context. Of course, you need to be careful not to
+ do anything while in context that would affect the lasting state
+ of your other objects, if you want things to return to their
+ starting state when exiting the context.
+
+ The starting time value of a new Time object is 0, converted to
+ the chosen time type. Here is an illustration of how time can be
+ manipulated using a Time object:
+
+ >>> time = Time(until=20, timestep=1)
+ >>> 'The initial time is %s' % time()
+ 'The initial time is 0'
+ >>> 'Setting the time to %s' % time(5)
+ 'Setting the time to 5'
+ >>> time += 5
+ >>> 'After incrementing by 5, the time is %s' % time()
+ 'After incrementing by 5, the time is 10'
+ >>> with time as t: # Entering a context
+ ... 'Time before iteration: %s' % t()
+ ... 'Iteration: %s' % [val for val in t]
+ ... 'Time after iteration: %s' % t()
+ ... t += 2
+ ... 'The until parameter may be exceeded outside iteration: %s' % t()
+ 'Time before iteration: 10'
+ 'Iteration: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]'
+ 'Time after iteration: 20'
+ 'The until parameter may be exceeded outside iteration: 22'
+ >>> 'After exiting the context the time is back to %s' % time()
+ 'After exiting the context the time is back to 10'
+ """
+
+ _infinitely_iterable = True
+
+ forever = Infinity()
+
+ label= String(default='Time', doc="""
+ The label given to the Time object. Can be used to convey
+ more specific notions of time as appropriate. For instance,
+ the label could be 'Simulation Time' or 'Duration'.""")
+
+
+ time_type = Parameter(default=int, constant=True, doc="""
+ Callable that Time will use to convert user-specified time
+ values into the current time; all times will be of the resulting
+ numeric type.
+
+ By default, time is of integer type, but you can supply any
+ arbitrary-precision type like a fixed-point decimal or a
+ rational, to allow fractional times. Floating-point times are
+ also allowed, but are not recommended because they will suffer
+ from accumulated rounding errors. For instance, incrementing
+ a floating-point value 0.0 by 0.05, 20 times, will not reach
+ 1.0 exactly. Instead, it will be slightly higher than 1.0,
+ because 0.05 cannot be represented exactly in a standard
+ floating point numeric type. Fixed-point or rational types
+ should be able to handle such computations exactly, avoiding
+ accumulation issues over long time intervals.
+
+ Some potentially useful exact number classes:
+
+ - int: Suitable if all times can be expressed as integers.
+
+ - Python's decimal.Decimal and fractions.Fraction classes:
+ widely available but slow and also awkward to specify times
+ (e.g. cannot simply type 0.05, but have to use a special
+ constructor or a string).
+
+ - fixedpoint.FixedPoint: Allows a natural representation of
+ times in decimal notation, but very slow and needs to be
+ installed separately.
+
+ - gmpy.mpq: Allows a natural representation of times in
+ decimal notation, and very fast because it uses the GNU
+ Multi-Precision library, but needs to be installed
+ separately and depends on a non-Python library. gmpy.mpq
+ is gmpy's rational type.
+ """)
+
+ timestep = Parameter(default=1.0,doc="""
+ Stepsize to be used with the iterator interface.
+ Time can be advanced or decremented by any value, not just
+ those corresponding to the stepsize, and so this value is only
+ a default.""")
+
+ until = Parameter(default=forever,doc="""
+ Declaration of an expected end to time values, if any. When
+ using the iterator interface, iteration will end before this
+ value is exceeded.""")
+
+ unit = String(default=None, doc="""
+ The units of the time dimensions. The default of None is set
+ as the global time function may on an arbitrary time base.
+
+ Typical values for the parameter are 'seconds' (the SI unit
+ for time) or subdivisions thereof (e.g. 'milliseconds').""")
+
+
+ def __init__(self, **params):
+ super().__init__(**params)
+ self._time = self.time_type(0)
+ self._exhausted = None
+ self._pushed_state = []
+
+
+ def __eq__(self, other):
+ if not isinstance(other, Time):
+ return False
+ self_params = (self.timestep,self.until)
+ other_params = (other.timestep,other.until)
+ if self_params != other_params:
+ return False
+ return True
+
+
+ def __ne__(self, other):
+ return not (self == other)
+
+
+ def __iter__(self): return self
+
+
+ def __next__(self):
+ timestep = self.time_type(self.timestep)
+
+ if self._exhausted is None:
+ self._exhausted = False
+ elif (self._time + timestep) <= self.until:
+ self._time += timestep
+ else:
+ self._exhausted = None
+ raise StopIteration
+ return self._time
+
+ def __call__(self, val=None, time_type=None):
+ """
+ When called with no arguments, returns the current time value.
+
+ When called with a specified val, sets the time to it.
+
+ When called with a specified time_type, changes the time_type
+ and sets the current time to the given val (which *must* be
+ specified) converted to that time type. To ensure that
+ the current state remains consistent, this is normally the only
+ way to change the time_type of an existing Time instance.
+ """
+
+ if time_type and val is None:
+ raise Exception("Please specify a value for the new time_type.")
+ if time_type:
+ type_param = self.param.objects('existing').get('time_type')
+ type_param.constant = False
+ self.time_type = time_type
+ type_param.constant = True
+ if val is not None:
+ self._time = self.time_type(val)
+
+ return self._time
+
+
+ def advance(self, val):
+ self += val
+
+
+ def __iadd__(self, other):
+ self._time = self._time + self.time_type(other)
+ return self
+
+
+ def __isub__(self, other):
+ self._time = self._time - self.time_type(other)
+ return self
+
+
+ def __enter__(self):
+ """Enter the context and push the current state."""
+ self._pushed_state.append((self._time, self.timestep, self.until))
+ self.in_context = True
+ return self
+
+
+ def __exit__(self, exc, *args):
+ """
+ Exit from the current context, restoring the previous state.
+ The StopIteration exception raised in context will force the
+ context to exit. Any other exception exc that is raised in the
+ block will not be caught.
+ """
+ (self._time, self.timestep, self.until) = self._pushed_state.pop()
+ self.in_context = len(self._pushed_state) != 0
+ if exc is StopIteration:
+ return True
+
+
+
+[docs]class Dynamic(Parameter):
+ """
+ Parameter whose value can be generated dynamically by a callable
+ object.
+
+ If a Parameter is declared as Dynamic, it can be set a callable
+ object (such as a function or callable class), and getting the
+ parameter's value will call that callable.
+
+ Note that at present, the callable object must allow attributes
+ to be set on itself.
+
+ If set as time_dependent, setting the Dynamic.time_fn allows the
+ production of dynamic values to be controlled: a new value will be
+ produced only if the current value of time_fn is different from
+ what it was the last time the parameter value was requested.
+
+ By default, the Dynamic parameters are not time_dependent so that
+ new values are generated on every call regardless of the time. The
+ default time_fn used when time_dependent is a single Time instance
+ that allows general manipulations of time. It may be set to some
+ other callable as required so long as a number is returned on each
+ call.
+ """
+
+ time_fn = Time()
+ time_dependent = False
+
+ @typing.overload
+ def __init__(
+ self, default=None, *,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] def __init__(self, default=Undefined, **params):
+ """
+ Call the superclass's __init__ and set instantiate=True if the
+ default is dynamic.
+ """
+ super().__init__(default=default, **params)
+
+ if callable(self.default):
+ self._set_instantiate(True)
+ self._initialize_generator(self.default)
+
+
+ def _initialize_generator(self,gen,obj=None):
+ """
+ Add 'last time' and 'last value' attributes to the generator.
+ """
+ # Could use a dictionary to hold these things.
+ if hasattr(obj,"_Dynamic_time_fn"):
+ gen._Dynamic_time_fn = obj._Dynamic_time_fn
+
+ gen._Dynamic_last = None
+ # Would have usede None for this, but can't compare a fixedpoint
+ # number with None (e.g. 1>None but FixedPoint(1)>None can't be done)
+ gen._Dynamic_time = -1
+
+ gen._saved_Dynamic_last = []
+ gen._saved_Dynamic_time = []
+
+
+ def __get__(self,obj,objtype):
+ """
+ Call the superclass's __get__; if the result is not dynamic
+ return that result, otherwise ask that result to produce a
+ value and return it.
+ """
+ gen = super().__get__(obj,objtype)
+
+ if not hasattr(gen,'_Dynamic_last'):
+ return gen
+ else:
+ return self._produce_value(gen)
+
+
+ @instance_descriptor
+ def __set__(self,obj,val):
+ """
+ Call the superclass's set and keep this parameter's
+ instantiate value up to date (dynamic parameters
+ must be instantiated).
+
+ If val is dynamic, initialize it as a generator.
+ """
+ super().__set__(obj,val)
+
+ dynamic = callable(val)
+ if dynamic: self._initialize_generator(val,obj)
+ if obj is None: self._set_instantiate(dynamic)
+
+
+ def _produce_value(self,gen,force=False):
+ """
+ Return a value from gen.
+
+ If there is no time_fn, then a new value will be returned
+ (i.e. gen will be asked to produce a new value).
+
+ If force is True, or the value of time_fn() is different from
+ what it was was last time _produce_value was called, a new
+ value will be produced and returned. Otherwise, the last value
+ gen produced will be returned.
+ """
+
+ if hasattr(gen,"_Dynamic_time_fn"):
+ time_fn = gen._Dynamic_time_fn
+ else:
+ time_fn = self.time_fn
+
+ if (time_fn is None) or (not self.time_dependent):
+ value = _produce_value(gen)
+ gen._Dynamic_last = value
+ else:
+
+ time = time_fn()
+
+ if force or time!=gen._Dynamic_time:
+ value = _produce_value(gen)
+ gen._Dynamic_last = value
+ gen._Dynamic_time = time
+ else:
+ value = gen._Dynamic_last
+
+ return value
+
+
+ def _value_is_dynamic(self,obj,objtype=None):
+ """
+ Return True if the parameter is actually dynamic (i.e. the
+ value is being generated).
+ """
+ return hasattr(super().__get__(obj,objtype),'_Dynamic_last')
+
+
+ def _inspect(self,obj,objtype=None):
+ """Return the last generated value for this parameter."""
+ gen=super().__get__(obj,objtype)
+
+ if hasattr(gen,'_Dynamic_last'):
+ return gen._Dynamic_last
+ else:
+ return gen
+
+
+ def _force(self,obj,objtype=None):
+ """Force a new value to be generated, and return it."""
+ gen=super().__get__(obj,objtype)
+
+ if hasattr(gen,'_Dynamic_last'):
+ return self._produce_value(gen,force=True)
+ else:
+ return gen
+
+
+import numbers
+def _is_number(obj):
+ if isinstance(obj, numbers.Number): return True
+ # The extra check is for classes that behave like numbers, such as those
+ # found in numpy, gmpy, etc.
+ elif (hasattr(obj, '__int__') and hasattr(obj, '__add__')): return True
+ # This is for older versions of gmpy
+ elif hasattr(obj, 'qdiv'): return True
+ else: return False
+
+
+[docs]def get_soft_bounds(bounds, softbounds):
+ """
+ For each soft bound (upper and lower), if there is a defined bound
+ (not equal to None) and does not exceed the hard bound, then it is
+ returned. Otherwise it defaults to the hard bound. The hard bound
+ could still be None.
+ """
+ if bounds is None:
+ hl, hu = (None, None)
+ else:
+ hl, hu = bounds
+
+ if softbounds is None:
+ sl, su = (None, None)
+ else:
+ sl, su = softbounds
+
+ if sl is None or (hl is not None and sl<hl):
+ l = hl
+ else:
+ l = sl
+
+ if su is None or (hu is not None and su>hu):
+ u = hu
+ else:
+ u = su
+
+ return (l, u)
+
+
+[docs]class Bytes(Parameter):
+ """
+ A Bytes Parameter, with a default value and optional regular
+ expression (regex) matching.
+
+ Similar to the String parameter, but instead of type string
+ this parameter only allows objects of type bytes (e.g. b'bytes').
+ """
+
+ __slots__ = ['regex']
+
+ _slot_defaults = _dict_update(
+ Parameter._slot_defaults, default=b"", regex=None, allow_None=False,
+ )
+
+
+ @typing.overload
+ def __init__(
+ self,
+ default=b"", *, regex=None, allow_None=False,
+ doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, regex=Undefined, allow_None=Undefined, **kwargs):
+ super().__init__(default=default, **kwargs)
+ self.regex = regex
+ self._validate(self.default)
+
+ def _validate_regex(self, val, regex):
+ if (val is None and self.allow_None):
+ return
+ if regex is not None and re.match(regex, val) is None:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} value {val!r} "
+ f"does not match regex {regex!r}."
+ )
+
+ def _validate_value(self, val, allow_None):
+ if allow_None and val is None:
+ return
+ if not isinstance(val, bytes):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes a byte string value, "
+ f"not value of {type(val)}."
+ )
+
+ def _validate(self, val):
+ self._validate_value(val, self.allow_None)
+ self._validate_regex(val, self.regex)
+
+
+class __compute_set_hook:
+ """Remove when set_hook is removed"""
+ def __call__(self, p):
+ return _identity_hook
+
+ def __repr__(self):
+ return repr(self.sig)
+
+ @property
+ def sig(self):
+ return None
+
+
+_compute_set_hook = __compute_set_hook()
+
+
+[docs]class Number(Dynamic):
+ """
+ A numeric Dynamic Parameter, with a default value and optional bounds.
+
+ There are two types of bounds: ``bounds`` and
+ ``softbounds``. ``bounds`` are hard bounds: the parameter must
+ have a value within the specified range. The default bounds are
+ (None,None), meaning there are actually no hard bounds. One or
+ both bounds can be set by specifying a value
+ (e.g. bounds=(None,10) means there is no lower bound, and an upper
+ bound of 10). Bounds are inclusive by default, but exclusivity
+ can be specified for each bound by setting inclusive_bounds
+ (e.g. inclusive_bounds=(True,False) specifies an exclusive upper
+ bound).
+
+ Number is also a type of Dynamic parameter, so its value
+ can be set to a callable to get a dynamically generated
+ number (see Dynamic).
+
+ When not being dynamically generated, bounds are checked when a
+ Number is created or set. Using a default value outside the hard
+ bounds, or one that is not numeric, results in an exception. When
+ being dynamically generated, bounds are checked when the value
+ of a Number is requested. A generated value that is not numeric,
+ or is outside the hard bounds, results in an exception.
+
+ As a special case, if allow_None=True (which is true by default if
+ the parameter has a default of None when declared) then a value
+ of None is also allowed.
+
+ A separate function set_in_bounds() is provided that will
+ silently crop the given value into the legal range, for use
+ in, for instance, a GUI.
+
+ ``softbounds`` are present to indicate the typical range of
+ the parameter, but are not enforced. Setting the soft bounds
+ allows, for instance, a GUI to know what values to display on
+ sliders for the Number.
+
+ Example of creating a Number::
+
+ AB = Number(default=0.5, bounds=(None,10), softbounds=(0,1), doc='Distance from A to B.')
+
+ """
+
+ __slots__ = ['bounds', 'softbounds', 'inclusive_bounds', 'set_hook', 'step']
+
+ _slot_defaults = _dict_update(
+ Dynamic._slot_defaults, default=0.0, bounds=None, softbounds=None,
+ inclusive_bounds=(True,True), step=None, set_hook=_compute_set_hook,
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=0.0, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined,
+ inclusive_bounds=Undefined, step=Undefined, set_hook=Undefined, **params):
+ """
+ Initialize this parameter object and store the bounds.
+
+ Non-dynamic default values are checked against the bounds.
+ """
+ super().__init__(default=default, **params)
+ self.set_hook = set_hook
+ self.bounds = bounds
+ self.inclusive_bounds = inclusive_bounds
+ self.softbounds = softbounds
+ self.step = step
+ self._validate(self.default)
+
+ def __get__(self, obj, objtype):
+ """
+ Same as the superclass's __get__, but if the value was
+ dynamically generated, check the bounds.
+ """
+ result = super().__get__(obj, objtype)
+
+ # Should be able to optimize this commonly used method by
+ # avoiding extra lookups (e.g. _value_is_dynamic() is also
+ # looking up 'result' - should just pass it in).
+ if self._value_is_dynamic(obj, objtype):
+ self._validate(result)
+ return result
+
+ def set_in_bounds(self,obj,val):
+ """
+ Set to the given value, but cropped to be within the legal bounds.
+ All objects are accepted, and no exceptions will be raised. See
+ crop_to_bounds for details on how cropping is done.
+ """
+ if not callable(val):
+ bounded_val = self.crop_to_bounds(val)
+ else:
+ bounded_val = val
+ super().__set__(obj, bounded_val)
+
+ def crop_to_bounds(self, val):
+ """
+ Return the given value cropped to be within the hard bounds
+ for this parameter.
+
+ If a numeric value is passed in, check it is within the hard
+ bounds. If it is larger than the high bound, return the high
+ bound. If it's smaller, return the low bound. In either case, the
+ returned value could be None. If a non-numeric value is passed
+ in, set to be the default value (which could be None). In no
+ case is an exception raised; all values are accepted.
+
+ As documented in https://github.com/holoviz/param/issues/80,
+ currently does not respect exclusive bounds, which would
+ strictly require setting to one less for integer values or
+ an epsilon less for floats.
+ """
+ # Values outside the bounds are silently cropped to
+ # be inside the bounds.
+ if _is_number(val):
+ if self.bounds is None:
+ return val
+ vmin, vmax = self.bounds
+ if vmin is not None:
+ if val < vmin:
+ return vmin
+
+ if vmax is not None:
+ if val > vmax:
+ return vmax
+
+ elif self.allow_None and val is None:
+ return val
+
+ else:
+ # non-numeric value sent in: reverts to default value
+ return self.default
+
+ return val
+
+ def _validate_bounds(self, val, bounds, inclusive_bounds):
+ if bounds is None or (val is None and self.allow_None) or callable(val):
+ return
+ vmin, vmax = bounds
+ incmin, incmax = inclusive_bounds
+ if vmax is not None:
+ if incmax is True:
+ if not val <= vmax:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be at most "
+ f"{vmax}, not {val}."
+ )
+ else:
+ if not val < vmax:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be less than "
+ f"{vmax}, not {val}."
+ )
+
+ if vmin is not None:
+ if incmin is True:
+ if not val >= vmin:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be at least "
+ f"{vmin}, not {val}."
+ )
+ else:
+ if not val > vmin:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be greater than "
+ f"{vmin}, not {val}."
+ )
+
+ def _validate_value(self, val, allow_None):
+ if (allow_None and val is None) or callable(val):
+ return
+
+ if not _is_number(val):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes numeric values, "
+ f"not {type(val)}."
+ )
+
+ def _validate_step(self, val, step):
+ if step is not None and not _is_number(step):
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'step')} can only be "
+ f"None or a numeric value, not {type(step)}."
+ )
+
+ def _validate(self, val):
+ """
+ Checks that the value is numeric and that it is within the hard
+ bounds; if not, an exception is raised.
+ """
+ self._validate_value(val, self.allow_None)
+ self._validate_step(val, self.step)
+ self._validate_bounds(val, self.bounds, self.inclusive_bounds)
+
+ def get_soft_bounds(self):
+ return get_soft_bounds(self.bounds, self.softbounds)
+
+ def __setstate__(self,state):
+ if 'step' not in state:
+ state['step'] = None
+
+ super().__setstate__(state)
+
+
+
+[docs]class Integer(Number):
+ """Numeric Parameter required to be an Integer"""
+
+ _slot_defaults = _dict_update(Number._slot_defaults, default=0)
+
+ def _validate_value(self, val, allow_None):
+ if callable(val):
+ return
+
+ if allow_None and val is None:
+ return
+
+ if not isinstance(val, _int_types):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be an integer, "
+ f"not {type(val)}."
+ )
+
+ def _validate_step(self, val, step):
+ if step is not None and not isinstance(step, int):
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'step')} can only be "
+ f"None or an integer value, not {type(step)}."
+ )
+
+
+
+[docs]class Magnitude(Number):
+ """Numeric Parameter required to be in the range [0.0-1.0]."""
+
+ _slot_defaults = _dict_update(Number._slot_defaults, default=1.0, bounds=(0.0,1.0))
+
+ @typing.overload
+ def __init__(
+ self,
+ default=1.0, *, bounds=(0.0, 1.0), softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined,
+ inclusive_bounds=Undefined, step=Undefined, set_hook=Undefined, **params):
+ super().__init__(
+ default=default, bounds=bounds, softbounds=softbounds,
+ inclusive_bounds=inclusive_bounds, step=step, set_hook=set_hook, **params
+ )
+
+
+[docs]class Boolean(Parameter):
+ """Binary or tristate Boolean Parameter."""
+
+ _slot_defaults = _dict_update(Parameter._slot_defaults, default=False)
+
+ @typing.overload
+ def __init__(
+ self,
+ default=False, *,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, **params):
+ super().__init__(default=default, **params)
+ self._validate(self.default)
+
+ def _validate_value(self, val, allow_None):
+ if allow_None:
+ if not isinstance(val, bool) and val is not None:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes a "
+ f"boolean value or None, not {val!r}."
+ )
+ elif not isinstance(val, bool):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be True or False, "
+ f"not {val!r}."
+ )
+
+ def _validate(self, val):
+ self._validate_value(val, self.allow_None)
+
+
+class __compute_length_of_default:
+ def __call__(self, p):
+ return len(p.default)
+
+ def __repr__(self):
+ return repr(self.sig)
+
+ @property
+ def sig(self):
+ return None
+
+
+_compute_length_of_default = __compute_length_of_default()
+
+
+[docs]class Tuple(Parameter):
+ """A tuple Parameter (e.g. ('a',7.6,[3,5])) with a fixed tuple length."""
+
+ __slots__ = ['length']
+
+ _slot_defaults = _dict_update(Parameter._slot_defaults, default=(0,0), length=_compute_length_of_default)
+
+ @typing.overload
+ def __init__(
+ self,
+ default=(0,0), *, length=None,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, length=Undefined, **params):
+ """
+ Initialize a tuple parameter with a fixed length (number of
+ elements). The length is determined by the initial default
+ value, if any, and must be supplied explicitly otherwise. The
+ length is not allowed to change after instantiation.
+ """
+ super().__init__(default=default, **params)
+ if length is Undefined and self.default is None:
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'length')} must be "
+ "specified if no default is supplied."
+ )
+ elif default is not Undefined and default:
+ self.length = len(default)
+ else:
+ self.length = length
+ self._validate(self.default)
+
+ def _validate_value(self, val, allow_None):
+ if val is None and allow_None:
+ return
+
+ if not isinstance(val, tuple):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes a tuple value, "
+ f"not {type(val)}."
+ )
+
+ def _validate_length(self, val, length):
+ if val is None and self.allow_None:
+ return
+
+ if not len(val) == length:
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'length')} is not "
+ f"of the correct length ({len(val)} instead of {length})."
+ )
+
+ def _validate(self, val):
+ self._validate_value(val, self.allow_None)
+ self._validate_length(val, self.length)
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ return list(value) # As JSON has no tuple representation
+
+ @classmethod
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ return tuple(value) # As JSON has no tuple representation
+
+
+[docs]class NumericTuple(Tuple):
+ """A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length."""
+
+ def _validate_value(self, val, allow_None):
+ super()._validate_value(val, allow_None)
+ if allow_None and val is None:
+ return
+ for n in val:
+ if _is_number(n):
+ continue
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes numeric "
+ f"values, not {type(n)}."
+ )
+
+
+[docs]class XYCoordinates(NumericTuple):
+ """A NumericTuple for an X,Y coordinate."""
+
+ _slot_defaults = _dict_update(NumericTuple._slot_defaults, default=(0.0, 0.0))
+
+ @typing.overload
+ def __init__(
+ self,
+ default=(0.0, 0.0), *, length=None,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] def __init__(self, default=Undefined, **params):
+ super().__init__(default=default, length=2, **params)
+
+
+[docs]class Callable(Parameter):
+ """
+ Parameter holding a value that is a callable object, such as a function.
+
+ A keyword argument instantiate=True should be provided when a
+ function object is used that might have state. On the other hand,
+ regular standalone functions cannot be deepcopied as of Python
+ 2.4, so instantiate must be False for those values.
+ """
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, **params):
+ super().__init__(default=default, **params)
+ self._validate(self.default)
+
+ def _validate_value(self, val, allow_None):
+ if (allow_None and val is None) or callable(val):
+ return
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes a callable object, "
+ f"not objects of {type(val)}."
+ )
+
+ def _validate(self, val):
+ self._validate_value(val, self.allow_None)
+
+
+[docs]class Action(Callable):
+ """
+ A user-provided function that can be invoked like a class or object method using ().
+ In a GUI, this might be mapped to a button, but it can be invoked directly as well.
+ """
+# Currently same implementation as Callable, but kept separate to allow different handling in GUIs
+
+
+def _is_abstract(class_):
+ try:
+ return class_.abstract
+ except AttributeError:
+ return False
+
+
+# Could be a method of ClassSelector.
+[docs]def concrete_descendents(parentclass):
+ """
+ Return a dictionary containing all subclasses of the specified
+ parentclass, including the parentclass. Only classes that are
+ defined in scripts that have been run or modules that have been
+ imported are included, so the caller will usually first do ``from
+ package import *``.
+
+ Only non-abstract classes will be included.
+ """
+ return {c.__name__:c for c in descendents(parentclass)
+ if not _is_abstract(c)}
+
+
+[docs]class Composite(Parameter):
+ """
+ A Parameter that is a composite of a set of other attributes of the class.
+
+ The constructor argument 'attribs' takes a list of attribute
+ names, which may or may not be Parameters. Getting the parameter
+ returns a list of the values of the constituents of the composite,
+ in the order specified. Likewise, setting the parameter takes a
+ sequence of values and sets the value of the constituent
+ attributes.
+
+ This Parameter type has not been tested with watchers and
+ dependencies, and may not support them properly.
+ """
+
+ __slots__ = ['attribs', 'objtype']
+
+ @typing.overload
+ def __init__(
+ self,
+ *, attribs=None,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, *, attribs=Undefined, **kw):
+ if attribs is Undefined:
+ attribs = []
+ super().__init__(default=Undefined, **kw)
+ self.attribs = attribs
+
+ def __get__(self, obj, objtype):
+ """
+ Return the values of all the attribs, as a list.
+ """
+ if obj is None:
+ return [getattr(objtype, a) for a in self.attribs]
+ else:
+ return [getattr(obj, a) for a in self.attribs]
+
+ def _validate_attribs(self, val, attribs):
+ if len(val) == len(attribs):
+ return
+ raise ValueError(
+ f"{_validate_error_prefix(self)} got the wrong number "
+ f"of values (needed {len(attribs)}, but got {len(val)})."
+ )
+
+ def _validate(self, val):
+ self._validate_attribs(val, self.attribs)
+
+ def _post_setter(self, obj, val):
+ if obj is None:
+ for a, v in zip(self.attribs, val):
+ setattr(self.objtype, a, v)
+ else:
+ for a, v in zip(self.attribs, val):
+ setattr(obj, a, v)
+
+
+class SelectorBase(Parameter):
+ """
+ Parameter whose value must be chosen from a list of possibilities.
+
+ Subclasses must implement get_range().
+ """
+
+ __abstract = True
+
+ def get_range(self):
+ raise NotImplementedError("get_range() must be implemented in subclasses.")
+
+
+class ListProxy(list):
+ """
+ Container that supports both list-style and dictionary-style
+ updates. Useful for replacing code that originally accepted lists
+ but needs to support dictionary access (typically for naming
+ items).
+ """
+
+ def __init__(self, iterable, parameter=None):
+ super().__init__(iterable)
+ self._parameter = parameter
+
+ def _warn(self, method):
+ clsname = type(self._parameter).__name__
+ get_logger().warning(
+ '{clsname}.objects{method} is deprecated if objects attribute '
+ 'was declared as a dictionary. Use `{clsname}.objects[label] '
+ '= value` instead.'.format(clsname=clsname, method=method)
+ )
+
+ @contextmanager
+ def _trigger(self, trigger=True):
+ trigger = 'objects' in self._parameter.watchers and trigger
+ old = dict(self._parameter.names) or list(self._parameter._objects)
+ yield
+ if trigger:
+ value = self._parameter.names or self._parameter._objects
+ self._parameter._trigger_event('objects', old, value)
+
+ def __getitem__(self, index):
+ if self._parameter.names:
+ return self._parameter.names[index]
+ return super().__getitem__(index)
+
+ def __setitem__(self, index, object, trigger=True):
+ if isinstance(index, (int, slice)):
+ if self._parameter.names:
+ self._warn('[index] = object')
+ with self._trigger():
+ super().__setitem__(index, object)
+ self._parameter._objects[index] = object
+ return
+ if self and not self._parameter.names:
+ self._parameter.names = _named_objs(self)
+ with self._trigger(trigger):
+ if index in self._parameter.names:
+ old = self._parameter.names[index]
+ idx = self.index(old)
+ super().__setitem__(idx, object)
+ self._parameter._objects[idx] = object
+ else:
+ super().append(object)
+ self._parameter._objects.append(object)
+ self._parameter.names[index] = object
+
+ def __eq__(self, other):
+ eq = super().__eq__(other)
+ if self._parameter.names and eq is NotImplemented:
+ return dict(zip(self._parameter.names, self)) == other
+ return eq
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def append(self, object):
+ if self._parameter.names:
+ self._warn('.append')
+ with self._trigger():
+ super().append(object)
+ self._parameter._objects.append(object)
+
+ def copy(self):
+ if self._parameter.names:
+ return self._parameter.names.copy()
+ return list(self)
+
+ def clear(self):
+ with self._trigger():
+ super().clear()
+ self._parameter._objects.clear()
+ self._parameter.names.clear()
+
+ def extend(self, objects):
+ if self._parameter.names:
+ self._warn('.append')
+ with self._trigger():
+ super().extend(objects)
+ self._parameter._objects.extend(objects)
+
+ def get(self, key, default=None):
+ if self._parameter.names:
+ return self._parameter.names.get(key, default)
+ return _named_objs(self).get(key, default)
+
+ def insert(self, index, object):
+ if self._parameter.names:
+ self._warn('.insert')
+ with self._trigger():
+ super().insert(index, object)
+ self._parameter._objects.insert(index, object)
+
+ def items(self):
+ if self._parameter.names:
+ return self._parameter.names.items()
+ return _named_objs(self).items()
+
+ def keys(self):
+ if self._parameter.names:
+ return self._parameter.names.keys()
+ return _named_objs(self).keys()
+
+ def pop(self, *args):
+ index = args[0] if args else -1
+ if isinstance(index, int):
+ with self._trigger():
+ super().pop(index)
+ object = self._parameter._objects.pop(index)
+ if self._parameter.names:
+ self._parameter.names = {
+ k: v for k, v in self._parameter.names.items()
+ if v is object
+ }
+ return
+ if self and not self._parameter.names:
+ raise ValueError(
+ 'Cannot pop an object from {clsname}.objects if '
+ 'objects was not declared as a dictionary.'
+ )
+ with self._trigger():
+ object = self._parameter.names.pop(*args)
+ super().remove(object)
+ self._parameter._objects.remove(object)
+ return object
+
+ def remove(self, object):
+ with self._trigger():
+ super().remove(object)
+ self._parameter._objects.remove(object)
+ if self._parameter.names:
+ copy = self._parameter.names.copy()
+ self._parameter.names.clear()
+ self._parameter.names.update({
+ k: v for k, v in copy.items() if v is not object
+ })
+
+ def update(self, objects, **items):
+ if not self._parameter.names:
+ self._parameter.names = _named_objs(self)
+ objects = objects.items() if isinstance(objects, dict) else objects
+ with self._trigger():
+ for i, o in enumerate(objects):
+ if not isinstance(o, collections.abc.Sequence):
+ raise TypeError(
+ f'cannot convert dictionary update sequence element #{i} to a sequence'
+ )
+ o = tuple(o)
+ n = len(o)
+ if n != 2:
+ raise ValueError(
+ f'dictionary update sequence element #{i} has length {n}; 2 is required'
+ )
+ k, v = o
+ self.__setitem__(k, v, trigger=False)
+ for k, v in items.items():
+ self.__setitem__(k, v, trigger=False)
+
+ def values(self):
+ if self._parameter.names:
+ return self._parameter.names.values()
+ return _named_objs(self).values()
+
+
+class __compute_selector_default:
+ """
+ Using a function instead of setting default to [] in _slot_defaults, as
+ if it were modified in place later, which would happen with check_on_set set to False,
+ then the object in _slot_defaults would itself be updated and the next Selector
+ instance created wouldn't have [] as the default but a populated list.
+ """
+ def __call__(self, p):
+ return []
+
+ def __repr__(self):
+ return repr(self.sig)
+
+ @property
+ def sig(self):
+ return []
+
+_compute_selector_default = __compute_selector_default()
+
+
+class __compute_selector_checking_default:
+ def __call__(self, p):
+ return len(p.objects) != 0
+
+ def __repr__(self):
+ return repr(self.sig)
+
+ @property
+ def sig(self):
+ return None
+
+_compute_selector_checking_default = __compute_selector_checking_default()
+
+
+class _SignatureSelector(Parameter):
+ # Needs docstring; why is this a separate mixin?
+ _slot_defaults = _dict_update(
+ SelectorBase._slot_defaults, _objects=_compute_selector_default,
+ compute_default_fn=None, check_on_set=_compute_selector_checking_default,
+ allow_None=None, instantiate=False, default=None,
+ )
+
+ @classmethod
+ def _modified_slots_defaults(cls):
+ defaults = super()._modified_slots_defaults()
+ defaults['objects'] = defaults.pop('_objects')
+ return defaults
+
+
+[docs]class Selector(SelectorBase, _SignatureSelector):
+ """
+ Parameter whose value must be one object from a list of possible objects.
+
+ By default, if no default is specified, picks the first object from
+ the provided set of objects, as long as the objects are in an
+ ordered data collection.
+
+ check_on_set restricts the value to be among the current list of
+ objects. By default, if objects are initially supplied,
+ check_on_set is True, whereas if no objects are initially
+ supplied, check_on_set is False. This can be overridden by
+ explicitly specifying check_on_set initially.
+
+ If check_on_set is True (either because objects are supplied
+ initially, or because it is explicitly specified), the default
+ (initial) value must be among the list of objects (unless the
+ default value is None).
+
+ The list of objects can be supplied as a list (appropriate for
+ selecting among a set of strings, or among a set of objects with a
+ "name" parameter), or as a (preferably ordered) dictionary from
+ names to objects. If a dictionary is supplied, the objects
+ will need to be hashable so that their names can be looked
+ up from the object value.
+
+ empty_default is an internal argument that does not have a slot.
+ """
+
+ __slots__ = ['_objects', 'compute_default_fn', 'check_on_set', 'names']
+
+ @typing.overload
+ def __init__(
+ self,
+ *, objects=[], default=None, instantiate=False, compute_default_fn=None,
+ check_on_set=None, allow_None=None, empty_default=False,
+ doc=None, label=None, precedence=None, constant=False, readonly=False,
+ pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False
+ ):
+ ...
+
+ # Selector is usually used to allow selection from a list of
+ # existing objects, therefore instantiate is False by default.
+[docs] @_deprecate_positional_args
+ def __init__(self, *, objects=Undefined, default=Undefined, instantiate=Undefined,
+ compute_default_fn=Undefined, check_on_set=Undefined,
+ allow_None=Undefined, empty_default=False, **params):
+
+ autodefault = Undefined
+ if objects is not Undefined and objects:
+ if isinstance(objects, dict):
+ autodefault = list(objects.values())[0]
+ elif isinstance(objects, list):
+ autodefault = objects[0]
+
+ default = autodefault if (not empty_default and default is Undefined) else default
+
+ self.objects = objects
+ self.compute_default_fn = compute_default_fn
+ self.check_on_set = check_on_set
+
+ super().__init__(
+ default=default, instantiate=instantiate, **params)
+ # Required as Parameter sets allow_None=True if default is None
+ if allow_None is Undefined:
+ self.allow_None = self._slot_defaults['allow_None']
+ else:
+ self.allow_None = allow_None
+ if self.default is not None:
+ self._validate_value(self.default)
+ self._update_state()
+
+ def _update_state(self):
+ if self.check_on_set is False and self.default is not None:
+ self._ensure_value_is_in_objects(self.default)
+
+ @property
+ def objects(self):
+ return ListProxy(self._objects, self)
+
+ @objects.setter
+ def objects(self, objects):
+ if isinstance(objects, collections.abc.Mapping):
+ self.names = objects
+ self._objects = list(objects.values())
+ else:
+ self.names = {}
+ self._objects = objects
+
+ # Note that if the list of objects is changed, the current value for
+ # this parameter in existing POs could be outside of the new range.
+
+ def compute_default(self):
+ """
+ If this parameter's compute_default_fn is callable, call it
+ and store the result in self.default.
+
+ Also removes None from the list of objects (if the default is
+ no longer None).
+ """
+ if self.default is None and callable(self.compute_default_fn):
+ self.default = self.compute_default_fn()
+ self._ensure_value_is_in_objects(self.default)
+
+ def _validate(self, val):
+ if not self.check_on_set:
+ self._ensure_value_is_in_objects(val)
+ return
+
+ self._validate_value(val)
+
+ def _validate_value(self, val):
+ if self.check_on_set and not (self.allow_None and val is None) and val not in self.objects:
+ items = []
+ limiter = ']'
+ length = 0
+ for item in self.objects:
+ string = str(item)
+ length += len(string)
+ if length < 200:
+ items.append(string)
+ else:
+ limiter = ', ...]'
+ break
+ items = '[' + ', '.join(items) + limiter
+ raise ValueError(
+ f"{_validate_error_prefix(self)} does not accept {val!r}; "
+ f"valid options include: {items!r}"
+ )
+
+ def _ensure_value_is_in_objects(self, val):
+ """
+ Make sure that the provided value is present on the objects list.
+ Subclasses can override if they support multiple items on a list,
+ to check each item instead.
+ """
+ if not (val in self.objects):
+ self._objects.append(val)
+
+ def get_range(self):
+ """
+ Return the possible objects to which this parameter could be set.
+
+ (Returns the dictionary {object.name: object}.)
+ """
+ return _named_objs(self._objects, self.names)
+
+
+class ObjectSelector(Selector):
+ """
+ Deprecated. Same as Selector, but with a different constructor for
+ historical reasons.
+ """
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, objects=[], instantiate=False, compute_default_fn=None,
+ check_on_set=None, allow_None=None, empty_default=False,
+ doc=None, label=None, precedence=None, constant=False, readonly=False,
+ pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False
+ ):
+ ...
+
+ @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, objects=Undefined, **kwargs):
+ super().__init__(objects=objects, default=default,
+ empty_default=True, **kwargs)
+
+
+[docs]class ClassSelector(SelectorBase):
+ """
+ Parameter allowing selection of either a subclass or an instance of a given set of classes.
+ By default, requires an instance, but if is_instance=False, accepts a class instead.
+ Both class and instance values respect the instantiate slot, though it matters only
+ for is_instance=True.
+ """
+
+ __slots__ = ['class_', 'is_instance']
+
+ _slot_defaults = _dict_update(SelectorBase._slot_defaults, instantiate=True, is_instance=True)
+
+ @typing.overload
+ def __init__(
+ self,
+ *, class_, default=None, instantiate=True, is_instance=True,
+ allow_None=False, doc=None, label=None, precedence=None,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, *, class_, default=Undefined, instantiate=Undefined, is_instance=Undefined, **params):
+ self.class_ = class_
+ self.is_instance = is_instance
+ super().__init__(default=default,instantiate=instantiate,**params)
+ self._validate(self.default)
+
+ def _validate(self, val):
+ super()._validate(val)
+ self._validate_class_(val, self.class_, self.is_instance)
+
+ def _validate_class_(self, val, class_, is_instance):
+ if (val is None and self.allow_None):
+ return
+ if isinstance(class_, tuple):
+ class_name = ('(%s)' % ', '.join(cl.__name__ for cl in class_))
+ else:
+ class_name = class_.__name__
+ if is_instance:
+ if not (isinstance(val, class_)):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} value must be an instance of {class_name}, not {val!r}.")
+ else:
+ if not (issubclass(val, class_)):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} value must be a subclass of {class_name}, not {val}.")
+
+ def get_range(self):
+ """
+ Return the possible types for this parameter's value.
+
+ (I.e. return `{name: <class>}` for all classes that are
+ concrete_descendents() of `self.class_`.)
+
+ Only classes from modules that have been imported are added
+ (see concrete_descendents()).
+ """
+ classes = self.class_ if isinstance(self.class_, tuple) else (self.class_,)
+ all_classes = {}
+ for cls in classes:
+ all_classes.update(concrete_descendents(cls))
+ d = OrderedDict((name, class_) for name,class_ in all_classes.items())
+ if self.allow_None:
+ d['None'] = None
+ return d
+
+
+[docs]class List(Parameter):
+ """
+ Parameter whose value is a list of objects, usually of a specified type.
+
+ The bounds allow a minimum and/or maximum length of
+ list to be enforced. If the item_type is non-None, all
+ items in the list are checked to be of that type.
+
+ `class_` is accepted as an alias for `item_type`, but is
+ deprecated due to conflict with how the `class_` slot is
+ used in Selector classes.
+ """
+
+ __slots__ = ['bounds', 'item_type', 'class_']
+
+ _slot_defaults = _dict_update(
+ Parameter._slot_defaults, class_=None, item_type=None, bounds=(0, None),
+ instantiate=True, default=[],
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=[], *, class_=None, item_type=None, instantiate=True, bounds=(0, None),
+ allow_None=False, doc=None, label=None, precedence=None,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, class_=Undefined, item_type=Undefined,
+ instantiate=Undefined, bounds=Undefined, **params):
+ if class_ is not Undefined:
+ # PARAM3_DEPRECATION
+ warnings.warn(
+ message="The 'class_' attribute on 'List' is deprecated. Use instead 'item_type'",
+ category=_ParamDeprecationWarning,
+ stacklevel=3,
+ )
+ if item_type is not Undefined and class_ is not Undefined:
+ self.item_type = item_type
+ elif item_type is Undefined or item_type is None:
+ self.item_type = class_
+ else:
+ self.item_type = item_type
+ self.class_ = self.item_type
+ self.bounds = bounds
+ Parameter.__init__(self, default=default, instantiate=instantiate,
+ **params)
+ self._validate(self.default)
+
+ def _validate(self, val):
+ """
+ Checks that the value is numeric and that it is within the hard
+ bounds; if not, an exception is raised.
+ """
+ self._validate_value(val, self.allow_None)
+ self._validate_bounds(val, self.bounds)
+ self._validate_item_type(val, self.item_type)
+
+ def _validate_bounds(self, val, bounds):
+ "Checks that the list is of the right length and has the right contents."
+ if bounds is None or (val is None and self.allow_None):
+ return
+ min_length, max_length = bounds
+ l = len(val)
+ if min_length is not None and max_length is not None:
+ if not (min_length <= l <= max_length):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} length must be between "
+ f"{min_length} and {max_length} (inclusive), not {l}."
+ )
+ elif min_length is not None:
+ if not min_length <= l:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} length must be at "
+ f"least {min_length}, not {l}."
+ )
+ elif max_length is not None:
+ if not l <= max_length:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} length must be at "
+ f"most {max_length}, not {l}."
+ )
+
+ def _validate_value(self, val, allow_None):
+ if allow_None and val is None:
+ return
+ if not isinstance(val, list):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} must be a list, not an "
+ f"object of {type(val)}."
+ )
+
+ def _validate_item_type(self, val, item_type):
+ if item_type is None or (self.allow_None and val is None):
+ return
+ for v in val:
+ if isinstance(v, item_type):
+ continue
+ raise TypeError(
+ f"{_validate_error_prefix(self)} items must be instances "
+ f"of {item_type!r}, not {type(v)}."
+ )
+
+
+[docs]class HookList(List):
+ """
+ Parameter whose value is a list of callable objects.
+
+ This type of List Parameter is typically used to provide a place
+ for users to register a set of commands to be called at a
+ specified place in some sequence of processing steps.
+ """
+ __slots__ = ['class_', 'bounds']
+
+ def _validate_value(self, val, allow_None):
+ super()._validate_value(val, allow_None)
+ if allow_None and val is None:
+ return
+ for v in val:
+ if callable(v):
+ continue
+ raise ValueError(
+ f"{_validate_error_prefix(self)} items must be callable, "
+ f"not {v!r}."
+ )
+
+
+[docs]class Dict(ClassSelector):
+ """
+ Parameter whose value is a dictionary.
+ """
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, is_instance=True,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=True,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] def __init__(self, default=Undefined, **params):
+ super().__init__(default=default, class_=dict, **params)
+
+
+[docs]class Array(ClassSelector):
+ """
+ Parameter whose value is a numpy array.
+ """
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, is_instance=True,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=True,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] def __init__(self, default=Undefined, **params):
+ from numpy import ndarray
+ super().__init__(default=default, class_=ndarray, **params)
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ return value.tolist()
+
+ @classmethod
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ import numpy
+ if isinstance(value, str):
+ return _deserialize_from_path(
+ {'.npy': numpy.load, '.txt': lambda x: numpy.loadtxt(str(x))},
+ value, 'Array'
+ )
+ else:
+ return numpy.asarray(value)
+
+
+[docs]class DataFrame(ClassSelector):
+ """
+ Parameter whose value is a pandas DataFrame.
+
+ The structure of the DataFrame can be constrained by the rows and
+ columns arguments:
+
+ rows: If specified, may be a number or an integer bounds tuple to
+ constrain the allowable number of rows.
+
+ columns: If specified, may be a number, an integer bounds tuple, a
+ list or a set. If the argument is numeric, constrains the number of
+ columns using the same semantics as used for rows. If either a list
+ or set of strings, the column names will be validated. If a set is
+ used, the supplied DataFrame must contain the specified columns and
+ if a list is given, the supplied DataFrame must contain exactly the
+ same columns and in the same order and no other columns.
+ """
+
+ __slots__ = ['rows', 'columns', 'ordered']
+
+ _slot_defaults = _dict_update(
+ ClassSelector._slot_defaults, rows=None, columns=None, ordered=None
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, rows=None, columns=None, ordered=None, is_instance=True,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=True,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, rows=Undefined, columns=Undefined, ordered=Undefined, **params):
+ from pandas import DataFrame as pdDFrame
+ self.rows = rows
+ self.columns = columns
+ self.ordered = ordered
+ super().__init__(default=default, class_=pdDFrame, **params)
+ self._validate(self.default)
+
+ def _length_bounds_check(self, bounds, length, name):
+ message = f'{name} length {length} does not match declared bounds of {bounds}'
+ if not isinstance(bounds, tuple):
+ if (bounds != length):
+ raise ValueError(f"{_validate_error_prefix(self)}: {message}")
+ else:
+ return
+ (lower, upper) = bounds
+ failure = ((lower is not None and (length < lower))
+ or (upper is not None and length > upper))
+ if failure:
+ raise ValueError(f"{_validate_error_prefix(self)}: {message}")
+
+ def _validate(self, val):
+ super()._validate(val)
+
+ if isinstance(self.columns, set) and self.ordered is True:
+ raise ValueError(
+ f'{_validate_error_prefix(self)}: columns cannot be ordered '
+ f'when specified as a set'
+ )
+
+ if self.allow_None and val is None:
+ return
+
+ if self.columns is None:
+ pass
+ elif (isinstance(self.columns, tuple) and len(self.columns)==2
+ and all(isinstance(v, (type(None), numbers.Number)) for v in self.columns)): # Numeric bounds tuple
+ self._length_bounds_check(self.columns, len(val.columns), 'columns')
+ elif isinstance(self.columns, (list, set)):
+ self.ordered = isinstance(self.columns, list) if self.ordered is None else self.ordered
+ difference = set(self.columns) - {str(el) for el in val.columns}
+ if difference:
+ raise ValueError(
+ f"{_validate_error_prefix(self)}: provided columns "
+ f"{list(val.columns)} does not contain required "
+ f"columns {sorted(self.columns)}"
+ )
+ else:
+ self._length_bounds_check(self.columns, len(val.columns), 'column')
+
+ if self.ordered:
+ if list(val.columns) != list(self.columns):
+ raise ValueError(
+ f"{_validate_error_prefix(self)}: provided columns "
+ f"{list(val.columns)} must exactly match {self.columns}"
+ )
+ if self.rows is not None:
+ self._length_bounds_check(self.rows, len(val), 'row')
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ return value.to_dict('records')
+
+ @classmethod
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ import pandas
+ if isinstance(value, str):
+ return _deserialize_from_path(
+ {
+ '.csv': pandas.read_csv,
+ '.dta': pandas.read_stata,
+ '.feather': pandas.read_feather,
+ '.h5': pandas.read_hdf,
+ '.hdf5': pandas.read_hdf,
+ '.json': pandas.read_json,
+ '.ods': pandas.read_excel,
+ '.parquet': pandas.read_parquet,
+ '.pkl': pandas.read_pickle,
+ '.tsv': lambda x: pandas.read_csv(x, sep='\t'),
+ '.xlsm': pandas.read_excel,
+ '.xlsx': pandas.read_excel,
+ }, value, 'DataFrame')
+ else:
+ return pandas.DataFrame(value)
+
+
+[docs]class Series(ClassSelector):
+ """
+ Parameter whose value is a pandas Series.
+
+ The structure of the Series can be constrained by the rows argument
+ which may be a number or an integer bounds tuple to constrain the
+ allowable number of rows.
+ """
+
+ __slots__ = ['rows']
+
+ _slot_defaults = _dict_update(
+ ClassSelector._slot_defaults, rows=None, allow_None=False
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, rows=None, allow_None=False, is_instance=True,
+ doc=None, label=None, precedence=None, instantiate=True,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, rows=Undefined, allow_None=Undefined, **params):
+ from pandas import Series as pdSeries
+ self.rows = rows
+ super().__init__(default=default, class_=pdSeries, allow_None=allow_None,
+ **params)
+ self._validate(self.default)
+
+ def _length_bounds_check(self, bounds, length, name):
+ message = f'{name} length {length} does not match declared bounds of {bounds}'
+ if not isinstance(bounds, tuple):
+ if (bounds != length):
+ raise ValueError(f"{_validate_error_prefix(self)}: {message}")
+ else:
+ return
+ (lower, upper) = bounds
+ failure = ((lower is not None and (length < lower))
+ or (upper is not None and length > upper))
+ if failure:
+ raise ValueError(f"{_validate_error_prefix(self)}: {message}")
+
+ def _validate(self, val):
+ super()._validate(val)
+
+ if self.allow_None and val is None:
+ return
+
+ if self.rows is not None:
+ self._length_bounds_check(self.rows, len(val), 'row')
+
+
+
+# For portable code:
+# - specify paths in unix (rather than Windows) style;
+# - use resolve_path(path_to_file=True) for paths to existing files to be read,
+# - use resolve_path(path_to_file=False) for paths to existing folders to be read,
+# and normalize_path() for paths to new files to be written.
+
+class resolve_path(ParameterizedFunction):
+ """
+ Find the path to an existing file, searching the paths specified
+ in the search_paths parameter if the filename is not absolute, and
+ converting a UNIX-style path to the current OS's format if
+ necessary.
+
+ To turn a supplied relative path into an absolute one, the path is
+ appended to paths in the search_paths parameter, in order, until
+ the file is found.
+
+ An IOError is raised if the file is not found.
+
+ Similar to Python's os.path.abspath(), except more search paths
+ than just os.getcwd() can be used, and the file must exist.
+ """
+
+ search_paths = List(default=[os.getcwd()], pickle_default_value=False, doc="""
+ Prepended to a non-relative path, in order, until a file is
+ found.""")
+
+ path_to_file = Boolean(default=True, pickle_default_value=False,
+ allow_None=True, doc="""
+ String specifying whether the path refers to a 'File' or a
+ 'Folder'. If None, the path may point to *either* a 'File' *or*
+ a 'Folder'.""")
+
+ def __call__(self, path, **params):
+ p = ParamOverrides(self, params)
+ path = os.path.normpath(path)
+ ftype = "File" if p.path_to_file is True \
+ else "Folder" if p.path_to_file is False else "Path"
+
+ if not p.search_paths:
+ p.search_paths = [os.getcwd()]
+
+ if os.path.isabs(path):
+ if ((p.path_to_file is None and os.path.exists(path)) or
+ (p.path_to_file is True and os.path.isfile(path)) or
+ (p.path_to_file is False and os.path.isdir( path))):
+ return path
+ raise OSError(f"{ftype} '{path}' not found.")
+
+ else:
+ paths_tried = []
+ for prefix in p.search_paths:
+ try_path = os.path.join(os.path.normpath(prefix), path)
+
+ if ((p.path_to_file is None and os.path.exists(try_path)) or
+ (p.path_to_file is True and os.path.isfile(try_path)) or
+ (p.path_to_file is False and os.path.isdir( try_path))):
+ return try_path
+
+ paths_tried.append(try_path)
+
+ raise OSError(ftype + " " + os.path.split(path)[1] + " was not found in the following place(s): " + str(paths_tried) + ".")
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+class normalize_path(ParameterizedFunction):
+ """
+ Convert a UNIX-style path to the current OS's format,
+ typically for creating a new file or directory.
+
+ If the path is not already absolute, it will be made absolute
+ (using the prefix parameter).
+
+ Should do the same as Python's os.path.abspath(), except using
+ prefix rather than os.getcwd).
+ """
+
+ prefix = String(default=os.getcwd(),pickle_default_value=False,doc="""
+ Prepended to the specified path, if that path is not
+ absolute.""")
+
+ def __call__(self,path="",**params):
+ p = ParamOverrides(self,params)
+
+ if not os.path.isabs(path):
+ path = os.path.join(os.path.normpath(p.prefix),path)
+
+ return os.path.normpath(path)
+
+
+
+[docs]class Path(Parameter):
+ """Parameter that can be set to a string specifying the path of a file or folder.
+
+ The string should be specified in UNIX style, but it will be
+ returned in the format of the user's operating system. Please use
+ the Filename or Foldername Parameters if you require discrimination
+ between the two possibilities.
+
+ The specified path can be absolute, or relative to either:
+
+ * any of the paths specified in the search_paths attribute (if
+ search_paths is not None);
+
+ or
+
+ * any of the paths searched by resolve_path() (if search_paths
+ is None).
+
+ Parameters
+ ----------
+ search_paths : list, default=[os.getcwd()]
+ List of paths to search the path from
+ check_exists: boolean, default=True
+ If True (default) the path must exist on instantiation and set,
+ otherwise the path can optionally exist.
+ """
+
+ __slots__ = ['search_paths', 'check_exists']
+
+ _slot_defaults = _dict_update(
+ Parameter._slot_defaults, check_exists=True,
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, search_paths=None, check_exists=True,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, search_paths=Undefined, check_exists=Undefined, **params):
+ if search_paths is Undefined:
+ search_paths = []
+
+ self.search_paths = search_paths
+ if check_exists is not Undefined and not isinstance(check_exists, bool):
+ raise ValueError("'check_exists' attribute value must be a boolean")
+ self.check_exists = check_exists
+ super().__init__(default,**params)
+ self._validate(self.default)
+
+ def _resolve(self, path):
+ return resolve_path(path, path_to_file=None, search_paths=self.search_paths)
+
+ def _validate(self, val):
+ if val is None:
+ if not self.allow_None:
+ raise ValueError(f'{_validate_error_prefix(self)} does not accept None')
+ else:
+ if not isinstance(val, (str, pathlib.Path)):
+ raise ValueError(f'{_validate_error_prefix(self)} only take str or pathlib.Path types')
+ try:
+ self._resolve(val)
+ except OSError as e:
+ if self.check_exists:
+ raise OSError(e.args[0]) from None
+
+ def __get__(self, obj, objtype):
+ """
+ Return an absolute, normalized path (see resolve_path).
+ """
+ raw_path = super().__get__(obj,objtype)
+ if raw_path is None:
+ path = None
+ else:
+ try:
+ path = self._resolve(raw_path)
+ except OSError:
+ if self.check_exists:
+ raise
+ else:
+ path = raw_path
+ return path
+
+ def __getstate__(self):
+ # don't want to pickle the search_paths
+ state = super().__getstate__()
+
+ if 'search_paths' in state:
+ state['search_paths'] = []
+
+ return state
+
+
+
+[docs]class Filename(Path):
+ """
+ Parameter that can be set to a string specifying the path of a file.
+
+ The string should be specified in UNIX style, but it will be
+ returned in the format of the user's operating system.
+
+ The specified path can be absolute, or relative to either:
+
+ * any of the paths specified in the search_paths attribute (if
+ search_paths is not None);
+
+ or
+
+ * any of the paths searched by resolve_path() (if search_paths
+ is None).
+ """
+
+ def _resolve(self, path):
+ return resolve_path(path, path_to_file=True, search_paths=self.search_paths)
+
+
+[docs]class Foldername(Path):
+ """
+ Parameter that can be set to a string specifying the path of a folder.
+
+ The string should be specified in UNIX style, but it will be
+ returned in the format of the user's operating system.
+
+ The specified path can be absolute, or relative to either:
+
+ * any of the paths specified in the search_paths attribute (if
+ search_paths is not None);
+
+ or
+
+ * any of the paths searched by resolve_dir_path() (if search_paths
+ is None).
+ """
+
+ def _resolve(self, path):
+ return resolve_path(path, path_to_file=False, search_paths=self.search_paths)
+
+
+
+def _abbreviate_paths(pathspec,named_paths):
+ """
+ Given a dict of (pathname,path) pairs, removes any prefix shared by all pathnames.
+ Helps keep menu items short yet unambiguous.
+ """
+ from os.path import commonprefix, dirname, sep
+
+ prefix = commonprefix([dirname(name)+sep for name in named_paths.keys()]+[pathspec])
+ return OrderedDict([(name[len(prefix):],path) for name,path in named_paths.items()])
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def abbreviate_paths(pathspec,named_paths):
+ """
+ Given a dict of (pathname,path) pairs, removes any prefix shared by all pathnames.
+ Helps keep menu items short yet unambiguous.
+
+ .. deprecated:: 2.0.0
+ """
+ return _abbreviate_paths(pathspec, named_paths)
+
+
+[docs]class FileSelector(Selector):
+ """
+ Given a path glob, allows one file to be selected from those matching.
+ """
+ __slots__ = ['path']
+
+ _slot_defaults = _dict_update(
+ Selector._slot_defaults, path="",
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, path="", objects=[], instantiate=False, compute_default_fn=None,
+ check_on_set=None, allow_None=None, empty_default=False,
+ doc=None, label=None, precedence=None, constant=False, readonly=False,
+ pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, path=Undefined, **kwargs):
+ self.default = default
+ self.path = path
+ self.update(path=path)
+ if default is not Undefined:
+ self.default = default
+ super().__init__(default=self.default, objects=self._objects, **kwargs)
+
+ def _on_set(self, attribute, old, new):
+ super()._on_set(attribute, new, old)
+ if attribute == 'path':
+ self.update(path=new)
+
+ def update(self, path=Undefined):
+ if path is Undefined:
+ path = self.path
+ if path == "":
+ self.objects = []
+ else:
+ # Convert using os.fspath and pathlib.Path to handle ensure
+ # the path separators are consistent (on Windows in particular)
+ pathpattern = os.fspath(pathlib.Path(path))
+ self.objects = sorted(glob.glob(pathpattern))
+ if self.default in self.objects:
+ return
+ self.default = self.objects[0] if self.objects else None
+
+ def get_range(self):
+ return _abbreviate_paths(self.path,super().get_range())
+
+
+[docs]class ListSelector(Selector):
+ """
+ Variant of Selector where the value can be multiple objects from
+ a list of possible objects.
+ """
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, objects=[], instantiate=False, compute_default_fn=None,
+ check_on_set=None, allow_None=None, empty_default=False,
+ doc=None, label=None, precedence=None, constant=False, readonly=False,
+ pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, objects=Undefined, **kwargs):
+ super().__init__(
+ objects=objects, default=default, empty_default=True, **kwargs)
+
+ def compute_default(self):
+ if self.default is None and callable(self.compute_default_fn):
+ self.default = self.compute_default_fn()
+ for o in self.default:
+ if o not in self.objects:
+ self.objects.append(o)
+
+ def _validate(self, val):
+ self._validate_type(val)
+
+ if self.check_on_set:
+ self._validate_value(val)
+ else:
+ self._ensure_value_is_in_objects(val)
+
+
+ def _validate_type(self, val):
+ if (val is None and self.allow_None):
+ return
+
+ if not isinstance(val, list):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes list types, "
+ f"not {val!r}."
+ )
+
+ def _validate_value(self, val):
+ self._validate_type(val)
+ if val is not None:
+ for o in val:
+ super()._validate_value(o)
+
+ def _update_state(self):
+ if self.check_on_set is False and self.default is not None:
+ for o in self.default:
+ self._ensure_value_is_in_objects(o)
+
+
+[docs]class MultiFileSelector(ListSelector):
+ """
+ Given a path glob, allows multiple files to be selected from the list of matches.
+ """
+ __slots__ = ['path']
+
+ _slot_defaults = _dict_update(
+ Selector._slot_defaults, path="",
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, path="", objects=[], compute_default_fn=None,
+ check_on_set=None, allow_None=None, empty_default=False,
+ doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True,
+ per_instance=True, allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, path=Undefined, **kwargs):
+ self.default = default
+ self.path = path
+ self.update(path=path)
+ super().__init__(default=default, objects=self._objects, **kwargs)
+
+ def _on_set(self, attribute, old, new):
+ super()._on_set(attribute, new, old)
+ if attribute == 'path':
+ self.update(path=new)
+
+ def update(self, path=Undefined):
+ if path is Undefined:
+ path = self.path
+ self.objects = sorted(glob.glob(path))
+ if self.default and all([o in self.objects for o in self.default]):
+ return
+ elif not self.default:
+ return
+ self.default = self.objects
+
+ def get_range(self):
+ return _abbreviate_paths(self.path,super().get_range())
+
+
+def _to_datetime(x):
+ """
+ Internal function that will convert date objs to datetime objs, used
+ for comparing date and datetime objects without error.
+ """
+ if isinstance(x, dt.date) and not isinstance(x, dt.datetime):
+ return dt.datetime(*x.timetuple()[:6])
+ return x
+
+
+class Date(Number):
+ """
+ Date parameter of datetime or date type.
+ """
+
+ _slot_defaults = _dict_update(Number._slot_defaults, default=None)
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+ def __init__(self, default=Undefined, **kwargs):
+ super().__init__(default=default, **kwargs)
+
+ def _validate_value(self, val, allow_None):
+ """
+ Checks that the value is numeric and that it is within the hard
+ bounds; if not, an exception is raised.
+ """
+ if self.allow_None and val is None:
+ return
+
+ if not isinstance(val, dt_types) and not (allow_None and val is None):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes datetime and "
+ f"date types, not {type(val)}."
+ )
+
+ def _validate_step(self, val, step):
+ if step is not None and not isinstance(step, dt_types):
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'step')} can only be None, "
+ f"a datetime or date type, not {type(step)}."
+ )
+
+ def _validate_bounds(self, val, bounds, inclusive_bounds):
+ val = _to_datetime(val)
+ bounds = None if bounds is None else map(_to_datetime, bounds)
+ return super()._validate_bounds(val, bounds, inclusive_bounds)
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ if not isinstance(value, (dt.datetime, dt.date)): # i.e np.datetime64
+ value = value.astype(dt.datetime)
+ return value.strftime("%Y-%m-%dT%H:%M:%S.%f")
+
+ @classmethod
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
+
+
+class CalendarDate(Number):
+ """
+ Parameter specifically allowing dates (not datetimes).
+ """
+
+ _slot_defaults = _dict_update(Number._slot_defaults, default=None)
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+ def __init__(self, default=Undefined, **kwargs):
+ super().__init__(default=default, **kwargs)
+
+ def _validate_value(self, val, allow_None):
+ """
+ Checks that the value is numeric and that it is within the hard
+ bounds; if not, an exception is raised.
+ """
+ if self.allow_None and val is None:
+ return
+
+ if (not isinstance(val, dt.date) or isinstance(val, dt.datetime)) and not (allow_None and val is None):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes date types."
+ )
+
+ def _validate_step(self, val, step):
+ if step is not None and not isinstance(step, dt.date):
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'step')} can only be None or "
+ f"a date type, not {type(step)}."
+ )
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ return value.strftime("%Y-%m-%d")
+
+ @classmethod
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ return dt.datetime.strptime(value, "%Y-%m-%d").date()
+
+
+[docs]class Color(Parameter):
+ """
+ Color parameter defined as a hex RGB string with an optional #
+ prefix or (optionally) as a CSS3 color name.
+ """
+
+ # CSS3 color specification https://www.w3.org/TR/css-color-3/#svg-color
+ _named_colors = [ 'aliceblue', 'antiquewhite', 'aqua',
+ 'aquamarine', 'azure', 'beige', 'bisque', 'black',
+ 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood',
+ 'cadetblue', 'chartreuse', 'chocolate', 'coral',
+ 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue',
+ 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey',
+ 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen',
+ 'darkorange', 'darkorchid', 'darkred', 'darksalmon',
+ 'darkseagreen', 'darkslateblue', 'darkslategray',
+ 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink',
+ 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue',
+ 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',
+ 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray',
+ 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink',
+ 'indianred', 'indigo', 'ivory', 'khaki', 'lavender',
+ 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue',
+ 'lightcoral', 'lightcyan', 'lightgoldenrodyellow',
+ 'lightgray', 'lightgrey', 'lightgreen', 'lightpink',
+ 'lightsalmon', 'lightseagreen', 'lightskyblue',
+ 'lightslategray', 'lightslategrey', 'lightsteelblue',
+ 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta',
+ 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid',
+ 'mediumpurple', 'mediumseagreen', 'mediumslateblue',
+ 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred',
+ 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
+ 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab',
+ 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen',
+ 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff',
+ 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red',
+ 'rosybrown', 'royalblue', 'saddlebrown', 'salmon',
+ 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver',
+ 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow',
+ 'springgreen', 'steelblue', 'tan', 'teal', 'thistle',
+ 'tomato', 'turquoise', 'violet', 'wheat', 'white',
+ 'whitesmoke', 'yellow', 'yellowgreen']
+
+ __slots__ = ['allow_named']
+
+ _slot_defaults = _dict_update(Parameter._slot_defaults, allow_named=True)
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, allow_named=True,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, allow_named=Undefined, **kwargs):
+ super().__init__(default=default, **kwargs)
+ self.allow_named = allow_named
+ self._validate(self.default)
+
+ def _validate(self, val):
+ self._validate_value(val, self.allow_None)
+ self._validate_allow_named(val, self.allow_named)
+
+ def _validate_value(self, val, allow_None):
+ if (allow_None and val is None):
+ return
+ if not isinstance(val, str):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} expects a string value, "
+ f"not an object of {type(val)}."
+ )
+
+ def _validate_allow_named(self, val, allow_named):
+ if (val is None and self.allow_None):
+ return
+ is_hex = re.match('^#?(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$', val)
+ if self.allow_named:
+ if not is_hex and val.lower() not in self._named_colors:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes RGB hex codes "
+ f"or named colors, received '{val}'."
+ )
+ elif not is_hex:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only accepts valid RGB hex "
+ f"codes, received {val!r}."
+ )
+
+
+[docs]class Range(NumericTuple):
+ """
+ A numeric range with optional bounds and softbounds.
+ """
+
+ __slots__ = ['bounds', 'inclusive_bounds', 'softbounds', 'step']
+
+ _slot_defaults = _dict_update(
+ NumericTuple._slot_defaults, default=None, bounds=None,
+ inclusive_bounds=(True,True), softbounds=None, step=None
+ )
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, length=None,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined,
+ inclusive_bounds=Undefined, step=Undefined, **params):
+ self.bounds = bounds
+ self.inclusive_bounds = inclusive_bounds
+ self.softbounds = softbounds
+ self.step = step
+ super().__init__(default=default,length=2,**params)
+
+ def _validate(self, val):
+ super()._validate(val)
+ self._validate_bounds(val, self.bounds, self.inclusive_bounds, 'bound')
+ self._validate_bounds(val, self.softbounds, self.inclusive_bounds, 'softbound')
+ self._validate_step(val, self.step)
+ self._validate_order(val, self.step, allow_None=self.allow_None)
+
+ def _validate_step(self, val, step):
+ if step is not None:
+ if not _is_number(step):
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'step')} can only be None "
+ f"or a numeric value, not {type(step)}."
+ )
+ elif step == 0:
+ raise ValueError(
+ f"{_validate_error_prefix(self, 'step')} cannot be 0."
+ )
+
+ def _validate_order(self, val, step, allow_None):
+ if val is None and allow_None:
+ return
+ elif val is not None and (val[0] is None or val[1] is None):
+ return
+
+ start, end = val
+ if step is not None and step > 0 and not start <= end:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} end {end} is less than its "
+ f"start {start} with positive step {step}."
+ )
+ elif step is not None and step < 0 and not start >= end:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} start {start} is less than its "
+ f"start {end} with negative step {step}."
+ )
+
+ def _validate_bound_type(self, value, position, kind):
+ if not _is_number(value):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} {position} {kind} can only be "
+ f"None or a numerical value, not {type(value)}."
+ )
+
+ def _validate_bounds(self, val, bounds, inclusive_bounds, kind):
+ if bounds is not None:
+ for pos, v in zip(['lower', 'upper'], bounds):
+ if v is None:
+ continue
+ self._validate_bound_type(v, pos, kind)
+ if kind == 'softbound':
+ return
+
+ if bounds is None or (val is None and self.allow_None):
+ return
+ vmin, vmax = bounds
+ incmin, incmax = inclusive_bounds
+ for bound, v in zip(['lower', 'upper'], val):
+ too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin)
+ too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax)
+ if too_low or too_high:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} {bound} bound must be in "
+ f"range {self.rangestr()}, not {v}."
+ )
+
+ def get_soft_bounds(self):
+ return get_soft_bounds(self.bounds, self.softbounds)
+
+ def rangestr(self):
+ vmin, vmax = self.bounds
+ incmin, incmax = self.inclusive_bounds
+ incmin = '[' if incmin else '('
+ incmax = ']' if incmax else ')'
+ return f'{incmin}{vmin}, {vmax}{incmax}'
+
+
+[docs]class DateRange(Range):
+ """
+ A datetime or date range specified as (start, end).
+
+ Bounds must be specified as datetime or date types (see param.dt_types).
+ """
+
+ def _validate_bound_type(self, value, position, kind):
+ if not isinstance(value, dt_types):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} {position} {kind} can only be "
+ f"None or a date/datetime value, not {type(value)}."
+ )
+
+ def _validate_bounds(self, val, bounds, inclusive_bounds, kind):
+ val = None if val is None else tuple(map(_to_datetime, val))
+ bounds = None if bounds is None else tuple(map(_to_datetime, bounds))
+ super()._validate_bounds(val, bounds, inclusive_bounds, kind)
+
+ def _validate_value(self, val, allow_None):
+ # Cannot use super()._validate_value as DateRange inherits from
+ # NumericTuple which check that the tuple values are numbers and
+ # datetime objects aren't numbers.
+ if allow_None and val is None:
+ return
+
+ if not isinstance(val, tuple):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes a tuple value, "
+ f"not {type(val)}."
+ )
+ for n in val:
+ if isinstance(n, dt_types):
+ continue
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes date/datetime "
+ f"values, not {type(n)}."
+ )
+
+ start, end = val
+ if not end >= start:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} end datetime {val[1]} "
+ f"is before start datetime {val[0]}."
+ )
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ # List as JSON has no tuple representation
+ serialized = []
+ for v in value:
+ if not isinstance(v, (dt.datetime, dt.date)): # i.e np.datetime64
+ v = v.astype(dt.datetime)
+ # Separate date and datetime to deserialize to the right type.
+ if type(v) == dt.date:
+ v = v.strftime("%Y-%m-%d")
+ else:
+ v = v.strftime("%Y-%m-%dT%H:%M:%S.%f")
+ serialized.append(v)
+ return serialized
+
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ deserialized = []
+ for v in value:
+ # Date
+ if len(v) == 10:
+ v = dt.datetime.strptime(v, "%Y-%m-%d").date()
+ # Datetime
+ else:
+ v = dt.datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f")
+ deserialized.append(v)
+ # As JSON has no tuple representation
+ return tuple(deserialized)
+
+[docs]class CalendarDateRange(Range):
+ """
+ A date range specified as (start_date, end_date).
+ """
+ def _validate_value(self, val, allow_None):
+ if allow_None and val is None:
+ return
+
+ for n in val:
+ if not isinstance(n, dt.date):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} only takes date types, "
+ f"not {val}."
+ )
+
+ start, end = val
+ if not end >= start:
+ raise ValueError(
+ f"{_validate_error_prefix(self)} end date {val[1]} is before "
+ f"start date {val[0]}."
+ )
+
+ def _validate_bound_type(self, value, position, kind):
+ if not isinstance(value, dt.date):
+ raise ValueError(
+ f"{_validate_error_prefix(self)} {position} {kind} can only be "
+ f"None or a date value, not {type(value)}."
+ )
+
+ @classmethod
+ def serialize(cls, value):
+ if value is None:
+ return None
+ # As JSON has no tuple representation
+ return [v.strftime("%Y-%m-%d") for v in value]
+
+ @classmethod
+ def deserialize(cls, value):
+ if value == 'null' or value is None:
+ return None
+ # As JSON has no tuple representation
+ return tuple([dt.datetime.strptime(v, "%Y-%m-%d").date() for v in value])
+
+
+[docs]class Event(Boolean):
+ """
+ An Event Parameter is one whose value is intimately linked to the
+ triggering of events for watchers to consume. Event has a Boolean
+ value, which when set to True triggers the associated watchers (as
+ any Parameter does) and then is automatically set back to
+ False. Conversely, if events are triggered directly via `.trigger`,
+ the value is transiently set to True (so that it's clear which of
+ many parameters being watched may have changed), then restored to
+ False when the triggering completes. An Event parameter is thus like
+ a momentary switch or pushbutton with a transient True value that
+ serves only to launch some other action (e.g. via a param.depends
+ decorator), rather than encapsulating the action itself as
+ param.Action does.
+ """
+
+ # _autotrigger_value specifies the value used to set the parameter
+ # to when the parameter is supplied to the trigger method. This
+ # value change is then what triggers the watcher callbacks.
+ __slots__ = ['_autotrigger_value', '_mode', '_autotrigger_reset_value']
+
+ @typing.overload
+ def __init__(
+ self,
+ default=False, *,
+ allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self,default=False,**params):
+ self._autotrigger_value = True
+ self._autotrigger_reset_value = False
+ self._mode = 'set-reset'
+ # Mode can be one of 'set', 'set-reset' or 'reset'
+
+ # 'set' is normal Boolean parameter behavior when set with a value.
+ # 'set-reset' temporarily sets the parameter (which triggers
+ # watching callbacks) but immediately resets the value back to
+ # False.
+ # 'reset' applies the reset from True to False without
+ # triggering watched callbacks
+
+ # This _mode attribute is one of the few places where a specific
+ # parameter has a special behavior that is relied upon by the
+ # core functionality implemented in
+ # parameterized.py. Specifically, the set_param method
+ # temporarily sets this attribute in order to disable resetting
+ # back to False while triggered callbacks are executing
+ super().__init__(default=default,**params)
+
+ def _reset_event(self, obj, val):
+ val = False
+ if obj is None:
+ self.default = val
+ else:
+ obj._param__private.values[self.name] = val
+ self._post_setter(obj, val)
+
+ @instance_descriptor
+ def __set__(self, obj, val):
+ if self._mode in ['set-reset', 'set']:
+ super().__set__(obj, val)
+ if self._mode in ['set-reset', 'reset']:
+ self._reset_event(obj, val)
+
+
+@contextmanager
+def exceptions_summarized():
+ """Useful utility for writing docs that need to show expected errors.
+ Shows exception only, concisely, without a traceback.
+ """
+ try:
+ yield
+ except Exception:
+ import sys
+ etype, value, tb = sys.exc_info()
+ print(f"{etype.__name__}: {value}", file=sys.stderr)
+
+from .reactive import bind, rx # noqa: api import
+
+import weakref
+
+from collections import defaultdict
+from functools import wraps
+
+from .parameterized import (
+ Parameter, Parameterized, ParameterizedMetaclass, transform_reference,
+)
+from ._utils import accept_arguments, iscoroutinefunction
+
+_display_accessors = {}
+_reactive_display_objs = weakref.WeakSet()
+
+def register_display_accessor(name, accessor, force=False):
+ if name in _display_accessors and not force:
+ raise KeyError(
+ 'Display accessor {name!r} already registered. Override it '
+ 'by setting force=True or unregister the existing accessor first.')
+ _display_accessors[name] = accessor
+ for fn in _reactive_display_objs:
+ setattr(fn, name, accessor(fn))
+
+def unregister_display_accessor(name):
+ if name not in _display_accessors:
+ raise KeyError('No such display accessor: {name!r}')
+ del _display_accessors[name]
+ for fn in _reactive_display_objs:
+ delattr(fn, name)
+
+
+[docs]@accept_arguments
+def depends(func, *dependencies, watch=False, on_init=False, **kw):
+ """Annotates a function or Parameterized method to express its dependencies.
+
+ The specified dependencies can be either be Parameter instances or if a
+ method is supplied they can be defined as strings referring to Parameters
+ of the class, or Parameters of subobjects (Parameterized objects that are
+ values of this object's parameters). Dependencies can either be on
+ Parameter values, or on other metadata about the Parameter.
+
+ Parameters
+ ----------
+ watch : bool, optional
+ Whether to invoke the function/method when the dependency is updated,
+ by default False
+ on_init : bool, optional
+ Whether to invoke the function/method when the instance is created,
+ by default False
+ """
+ dependencies, kw = (
+ tuple(transform_reference(arg) for arg in dependencies),
+ {key: transform_reference(arg) for key, arg in kw.items()}
+ )
+
+ if iscoroutinefunction(func):
+ @wraps(func)
+ async def _depends(*args, **kw):
+ return await func(*args, **kw)
+ else:
+ @wraps(func)
+ def _depends(*args, **kw):
+ return func(*args, **kw)
+
+ deps = list(dependencies)+list(kw.values())
+ string_specs = False
+ for dep in deps:
+ if isinstance(dep, str):
+ string_specs = True
+ elif hasattr(dep, '_dinfo'):
+ pass
+ elif not isinstance(dep, Parameter):
+ raise ValueError('The depends decorator only accepts string '
+ 'types referencing a parameter or parameter '
+ 'instances, found %s type instead.' %
+ type(dep).__name__)
+ elif not (isinstance(dep.owner, Parameterized) or
+ (isinstance(dep.owner, ParameterizedMetaclass))):
+ owner = 'None' if dep.owner is None else '%s class' % type(dep.owner).__name__
+ raise ValueError('Parameters supplied to the depends decorator, '
+ 'must be bound to a Parameterized class or '
+ 'instance, not %s.' % owner)
+
+ if (any(isinstance(dep, Parameter) for dep in deps) and
+ any(isinstance(dep, str) for dep in deps)):
+ raise ValueError('Dependencies must either be defined as strings '
+ 'referencing parameters on the class defining '
+ 'the decorated method or as parameter instances. '
+ 'Mixing of string specs and parameter instances '
+ 'is not supported.')
+ elif string_specs and kw:
+ raise AssertionError('Supplying keywords to the decorated method '
+ 'or function is not supported when referencing '
+ 'parameters by name.')
+
+ if not string_specs and watch: # string_specs case handled elsewhere (later), in Parameterized.__init__
+ if iscoroutinefunction(func):
+ async def cb(*events):
+ args = (getattr(dep.owner, dep.name) for dep in dependencies)
+ dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()}
+ await func(*args, **dep_kwargs)
+ else:
+ def cb(*events):
+ args = (getattr(dep.owner, dep.name) for dep in dependencies)
+ dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()}
+ return func(*args, **dep_kwargs)
+
+ grouped = defaultdict(list)
+ for dep in deps:
+ grouped[id(dep.owner)].append(dep)
+ for group in grouped.values():
+ group[0].owner.param.watch(cb, [dep.name for dep in group])
+
+ _dinfo = getattr(func, '_dinfo', {})
+ _dinfo.update({'dependencies': dependencies,
+ 'kw': kw, 'watch': watch, 'on_init': on_init})
+
+ _depends._dinfo = _dinfo
+
+ return _depends
+
+"""
+Generic support for objects with full-featured Parameters and
+messaging.
+
+This file comes from the Param library (https://github.com/holoviz/param)
+but can be taken out of the param module and used on its own if desired,
+either alone (providing basic Parameter support) or with param's
+__init__.py (providing specialized Parameter types).
+"""
+
+import asyncio
+import copy
+import datetime as dt
+import html
+import inspect
+import logging
+import numbers
+import operator
+import random
+import re
+import types
+import typing
+import warnings
+
+# Allow this file to be used standalone if desired, albeit without JSON serialization
+try:
+ from . import serializer
+except ImportError:
+ serializer = None
+
+from collections import defaultdict, namedtuple, OrderedDict
+from functools import partial, wraps, reduce
+from html import escape
+from itertools import chain
+from operator import itemgetter, attrgetter
+from types import FunctionType, MethodType
+
+from contextlib import contextmanager
+from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
+
+from ._utils import (
+ DEFAULT_SIGNATURE,
+ ParamDeprecationWarning as _ParamDeprecationWarning,
+ ParamFutureWarning as _ParamFutureWarning,
+ _deprecated,
+ _deprecate_positional_args,
+ _dict_update,
+ _is_auto_name,
+ _is_mutable_container,
+ _recursive_repr,
+ _validate_error_prefix,
+ accept_arguments,
+ iscoroutinefunction
+)
+
+try:
+ get_ipython()
+except NameError:
+ param_pager = None
+else:
+ # In case the optional ipython module is unavailable
+ try:
+ from .ipython import ParamPager
+ param_pager = ParamPager(metaclass=True) # Generates param description
+ except:
+ param_pager = None
+
+from inspect import getfullargspec
+
+dt_types = (dt.datetime, dt.date)
+_int_types = (int,)
+
+try:
+ import numpy as np
+ dt_types = dt_types + (np.datetime64,)
+ _int_types = _int_types + (np.integer,)
+except:
+ pass
+
+VERBOSE = INFO - 1
+logging.addLevelName(VERBOSE, "VERBOSE")
+
+# Get the appropriate logging.Logger instance. If `logger` is None, a
+# logger named `"param"` will be instantiated. If `name` is set, a descendant
+# logger with the name ``"param.<name>"`` is returned (or
+# ``logger.name + ".<name>"``)
+logger = None
+[docs]def get_logger(name=None):
+ if logger is None:
+ root_logger = logging.getLogger('param')
+ if not root_logger.handlers:
+ root_logger.setLevel(logging.INFO)
+ formatter = logging.Formatter(
+ fmt='%(levelname)s:%(name)s: %(message)s')
+ handler = logging.StreamHandler()
+ handler.setFormatter(formatter)
+ root_logger.addHandler(handler)
+ else:
+ root_logger = logger
+ if name is None:
+ return root_logger
+ else:
+ return logging.getLogger(root_logger.name + '.' + name)
+
+
+# Indicates whether warnings should be raised as errors, stopping
+# processing.
+warnings_as_exceptions = False
+
+docstring_signature = True # Add signature to class docstrings
+docstring_describe_params = True # Add parameter description to class
+ # docstrings (requires ipython module)
+object_count = 0
+warning_count = 0
+
+# Hook to apply to depends and bind arguments to turn them into valid parameters
+_reference_transforms = []
+
+def register_reference_transform(transform):
+ """
+ Appends a transform to extract potential parameter dependencies
+ from an object.
+
+ Arguments
+ ---------
+ transform: Callable[Any, Any]
+ """
+ return _reference_transforms.append(transform)
+
+def transform_reference(arg):
+ """
+ Applies transforms to turn objects which should be treated like
+ a parameter reference into a valid reference that can be resolved
+ by Param. This is useful for adding handling for depending on objects
+ that are not simple Parameters or functions with dependency
+ definitions.
+ """
+ for transform in _reference_transforms:
+ if isinstance(arg, Parameter) or hasattr(arg, '_dinfo'):
+ break
+ arg = transform(arg)
+ return arg
+
+def eval_function_with_deps(function):
+ """Evaluates a function after resolving its dependencies.
+
+ Calls and returns a function after resolving any dependencies
+ stored on the _dinfo attribute and passing the resolved values
+ as arguments.
+ """
+ args, kwargs = (), {}
+ if hasattr(function, '_dinfo'):
+ arg_deps = function._dinfo['dependencies']
+ kw_deps = function._dinfo.get('kw', {})
+ if kw_deps or any(isinstance(d, Parameter) for d in arg_deps):
+ args = (getattr(dep.owner, dep.name) for dep in arg_deps)
+ kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw_deps.items()}
+ return function(*args, **kwargs)
+
+def resolve_value(value):
+ """
+ Resolves the current value of a dynamic reference.
+ """
+ if isinstance(value, (list, tuple)):
+ return type(value)(resolve_value(v) for v in value)
+ elif isinstance(value, dict):
+ return type(value)((resolve_value(k), resolve_value(v)) for k, v in value.items())
+ elif isinstance(value, slice):
+ return slice(
+ resolve_value(value.start),
+ resolve_value(value.stop),
+ resolve_value(value.step)
+ )
+ value = transform_reference(value)
+ if hasattr(value, '_dinfo'):
+ value = eval_function_with_deps(value)
+ elif isinstance(value, Parameter):
+ value = getattr(value.owner, value.name)
+ return value
+
+def resolve_ref(reference, recursive=False):
+ """
+ Resolves all parameters a dynamic reference depends on.
+ """
+ if recursive:
+ if isinstance(reference, (list, tuple, set)):
+ return [r for v in reference for r in resolve_ref(v)]
+ elif isinstance(reference, dict):
+ return [r for kv in reference.items() for o in kv for r in resolve_ref(o)]
+ elif isinstance(reference, slice):
+ return [r for v in (reference.start, reference.stop, reference.step) for r in resolve_ref(v)]
+ reference = transform_reference(reference)
+ if hasattr(reference, '_dinfo'):
+ dinfo = getattr(reference, '_dinfo', {})
+ args = list(dinfo.get('dependencies', []))
+ kwargs = list(dinfo.get('kw', {}).values())
+ refs = []
+ for arg in (args + kwargs):
+ if isinstance(arg, str):
+ owner = get_method_owner(reference)
+ if arg in owner.param:
+ arg = owner.param[arg]
+ elif '.' in arg:
+ path = arg.split('.')
+ arg = owner
+ for attr in path[:-1]:
+ arg = getattr(arg, attr)
+ arg = arg.param[path[-1]]
+ else:
+ arg = getattr(owner, arg)
+ refs.extend(resolve_ref(arg))
+ return refs
+ elif isinstance(reference, Parameter):
+ return [reference]
+ return []
+
+def _identity_hook(obj, val):
+ """To be removed when set_hook is removed"""
+ return val
+
+
+class _Undefined:
+ """
+ Dummy value to signal completely undefined values rather than
+ simple None values.
+ """
+
+ def __bool__(self):
+ # Haven't defined whether Undefined is falsy or truthy,
+ # so to avoid subtle bugs raise an error when it
+ # is used in a comparison without `is`.
+ raise RuntimeError('Use `is` to compare Undefined')
+
+ def __repr__(self):
+ return '<Undefined>'
+
+
+Undefined = _Undefined()
+
+
+[docs]@contextmanager
+def logging_level(level):
+ """
+ Temporarily modify param's logging level.
+ """
+ level = level.upper()
+ levels = [DEBUG, INFO, WARNING, ERROR, CRITICAL, VERBOSE]
+ level_names = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'VERBOSE']
+
+ if level not in level_names:
+ raise Exception(f"Level {level!r} not in {levels!r}")
+
+ param_logger = get_logger()
+ logging_level = param_logger.getEffectiveLevel()
+ param_logger.setLevel(levels[level_names.index(level)])
+ try:
+ yield None
+ finally:
+ param_logger.setLevel(logging_level)
+
+
+@contextmanager
+def _batch_call_watchers(parameterized, enable=True, run=True):
+ """
+ Internal version of batch_call_watchers, adding control over queueing and running.
+ Only actually batches events if enable=True; otherwise a no-op. Only actually
+ calls the accumulated watchers on exit if run=True; otherwise they remain queued.
+ """
+ BATCH_WATCH = parameterized.param._BATCH_WATCH
+ parameterized.param._BATCH_WATCH = enable or parameterized.param._BATCH_WATCH
+ try:
+ yield
+ finally:
+ parameterized.param._BATCH_WATCH = BATCH_WATCH
+ if run and not BATCH_WATCH:
+ parameterized.param._batch_call_watchers()
+
+
+# PARAM3_DEPRECATION
+[docs]@_deprecated(extra_msg="Use instead `batch_call_watchers`.")
+@contextmanager
+def batch_watch(parameterized, enable=True, run=True):
+ with _batch_call_watchers(parameterized, enable, run):
+ yield
+
+
+[docs]@contextmanager
+def batch_call_watchers(parameterized):
+ """
+ Context manager to batch events to provide to Watchers on a
+ parameterized object. This context manager queues any events
+ triggered by setting a parameter on the supplied parameterized
+ object, saving them up to dispatch them all at once when the
+ context manager exits.
+ """
+ BATCH_WATCH = parameterized.param._BATCH_WATCH
+ parameterized.param._BATCH_WATCH = True
+ try:
+ yield
+ finally:
+ parameterized.param._BATCH_WATCH = BATCH_WATCH
+ if not BATCH_WATCH:
+ parameterized.param._batch_call_watchers()
+
+
+@contextmanager
+def _syncing(parameterized, parameters):
+ old = parameterized._param__private.syncing
+ parameterized._param__private.syncing = set(old) | set(parameters)
+ try:
+ yield
+ finally:
+ parameterized._param__private.syncing = old
+
+
+[docs]@contextmanager
+def edit_constant(parameterized):
+ """
+ Temporarily set parameters on Parameterized object to constant=False
+ to allow editing them.
+ """
+ params = parameterized.param.objects('existing').values()
+ constants = [p.constant for p in params]
+ for p in params:
+ p.constant = False
+ try:
+ yield
+ except:
+ raise
+ finally:
+ for (p, const) in zip(params, constants):
+ p.constant = const
+
+
+[docs]@contextmanager
+def discard_events(parameterized):
+ """
+ Context manager that discards any events within its scope
+ triggered on the supplied parameterized object.
+ """
+ batch_watch = parameterized.param._BATCH_WATCH
+ parameterized.param._BATCH_WATCH = True
+ watchers, events = (list(parameterized.param._state_watchers),
+ list(parameterized.param._events))
+ try:
+ yield
+ except:
+ raise
+ finally:
+ parameterized.param._BATCH_WATCH = batch_watch
+ parameterized.param._state_watchers = watchers
+ parameterized.param._events = events
+
+
+# External components can register an async executor which will run
+# async functions
+async_executor = None
+
+
+def classlist(class_):
+ """
+ Return a list of the class hierarchy above (and including) the given class.
+
+ Same as `inspect.getmro(class_)[::-1]`
+ """
+ return inspect.getmro(class_)[::-1]
+
+
+def descendents(class_):
+ """
+ Return a list of the class hierarchy below (and including) the given class.
+
+ The list is ordered from least- to most-specific. Can be useful for
+ printing the contents of an entire class hierarchy.
+ """
+ assert isinstance(class_,type)
+ q = [class_]
+ out = []
+ while len(q):
+ x = q.pop(0)
+ out.insert(0,x)
+ for b in x.__subclasses__():
+ if b not in q and b not in out:
+ q.append(b)
+ return out[::-1]
+
+
+def get_all_slots(class_):
+ """
+ Return a list of slot names for slots defined in `class_` and its
+ superclasses.
+ """
+ # A subclass's __slots__ attribute does not contain slots defined
+ # in its superclass (the superclass' __slots__ end up as
+ # attributes of the subclass).
+ all_slots = []
+ parent_param_classes = [c for c in classlist(class_)[1::]]
+ for c in parent_param_classes:
+ if hasattr(c,'__slots__'):
+ all_slots+=c.__slots__
+ return all_slots
+
+
+def get_occupied_slots(instance):
+ """
+ Return a list of slots for which values have been set.
+
+ (While a slot might be defined, if a value for that slot hasn't
+ been set, then it's an AttributeError to request the slot's
+ value.)
+ """
+ return [slot for slot in get_all_slots(type(instance))
+ if hasattr(instance,slot)]
+
+
+# PARAM3_DEPRECATION
+@_deprecated()
+def all_equal(arg1,arg2):
+ """
+ Return a single boolean for arg1==arg2, even for numpy arrays
+ using element-wise comparison.
+
+ Uses all(arg1==arg2) for sequences, and arg1==arg2 otherwise.
+
+ If both objects have an '_infinitely_iterable' attribute, they are
+ not be zipped together and are compared directly instead.
+ """
+ if all(hasattr(el, '_infinitely_iterable') for el in [arg1,arg2]):
+ return arg1==arg2
+ try:
+ return all(a1 == a2 for a1, a2 in zip(arg1, arg2))
+ except TypeError:
+ return arg1==arg2
+
+
+# PARAM3_DEPRECATION
+# The syntax to use a metaclass changed incompatibly between 2 and
+# 3. The add_metaclass() class decorator below creates a class using a
+# specified metaclass in a way that works on both 2 and 3. For 3, can
+# remove this decorator and specify metaclasses in a simpler way
+# (https://docs.python.org/3/reference/datamodel.html#customizing-class-creation)
+#
+# Code from six (https://bitbucket.org/gutworth/six; version 1.4.1).
+@_deprecated()
+def add_metaclass(metaclass):
+ """Class decorator for creating a class with a metaclass.
+
+ .. deprecated:: 2.0.0
+ """
+ def wrapper(cls):
+ orig_vars = cls.__dict__.copy()
+ orig_vars.pop('__dict__', None)
+ orig_vars.pop('__weakref__', None)
+ for slots_var in orig_vars.get('__slots__', ()):
+ orig_vars.pop(slots_var)
+ return metaclass(cls.__name__, cls.__bases__, orig_vars)
+ return wrapper
+
+
+class bothmethod:
+ """
+ 'optional @classmethod'
+
+ A decorator that allows a method to receive either the class
+ object (if called on the class) or the instance object
+ (if called on the instance) as its first argument.
+ """
+ def __init__(self, method):
+ self.method = method
+
+ def __get__(self, instance, owner):
+ if instance is None:
+ # Class call
+ return self.method.__get__(owner)
+ else:
+ # Instance call
+ return self.method.__get__(instance, owner)
+
+
+def _getattrr(obj, attr, *args):
+ def _getattr(obj, attr):
+ return getattr(obj, attr, *args)
+ return reduce(_getattr, [obj] + attr.split('.'))
+
+
+def no_instance_params(cls):
+ """
+ Disables instance parameters on the class
+ """
+ cls._param__private.disable_instance_params = True
+ return cls
+
+
+def _instantiate_param_obj(paramobj, owner=None):
+ """Return a Parameter object suitable for instantiation given the class's Parameter object"""
+
+ # Shallow-copy Parameter object without the watchers
+ p = copy.copy(paramobj)
+ p.owner = owner
+
+ # Reset watchers since class parameter watcher should not execute
+ # on instance parameters
+ p.watchers = {}
+
+ # shallow-copy any mutable slot values other than the actual default
+ for s in p.__class__.__slots__:
+ v = getattr(p, s)
+ if _is_mutable_container(v) and s != "default":
+ setattr(p, s, copy.copy(v))
+ return p
+
+
+def _instantiated_parameter(parameterized, param):
+ """
+ Given a Parameterized object and one of its class Parameter objects,
+ return the appropriate Parameter object for this instance, instantiating
+ it if need be.
+ """
+ if (getattr(parameterized._param__private, 'initialized', False) and param.per_instance and
+ not getattr(type(parameterized)._param__private, 'disable_instance_params', False)):
+ key = param.name
+
+ if key not in parameterized._param__private.params:
+ parameterized._param__private.params[key] = _instantiate_param_obj(param, parameterized)
+
+ param = parameterized._param__private.params[key]
+
+ return param
+
+
+def instance_descriptor(f):
+ # If parameter has an instance Parameter, delegate setting
+ def _f(self, obj, val):
+ # obj is None when the metaclass is setting
+ if obj is not None:
+ instance_param = obj._param__private.params.get(self.name)
+ if instance_param is None:
+ instance_param = _instantiated_parameter(obj, self)
+ if instance_param is not None and self is not instance_param:
+ instance_param.__set__(obj, val)
+ return
+ return f(self, obj, val)
+ return _f
+
+
+def get_method_owner(method):
+ """
+ Gets the instance that owns the supplied method
+ """
+ if not inspect.ismethod(method):
+ return None
+ if isinstance(method, partial):
+ method = method.func
+ return method.__self__
+
+
+# PARAM3_DEPRECATION
+def recursive_repr(fillvalue='...'):
+ """
+ Decorator to make a repr function return fillvalue for a recursive call
+
+ .. deprecated:: 1.12.0
+ """
+ warnings.warn(
+ 'recursive_repr has been deprecated and will be removed in a future version.',
+ category=_ParamDeprecationWarning,
+ stacklevel=2,
+ )
+ return _recursive_repr(fillvalue=fillvalue)
+
+
+[docs]@accept_arguments
+def output(func, *output, **kw):
+ """
+ output allows annotating a method on a Parameterized class to
+ declare that it returns an output of a specific type. The outputs
+ of a Parameterized class can be queried using the
+ Parameterized.param.outputs method. By default the output will
+ inherit the method name but a custom name can be declared by
+ expressing the Parameter type using a keyword argument.
+
+ The simplest declaration simply declares the method returns an
+ object without any type guarantees, e.g.:
+
+ @output()
+
+ If a specific parameter type is specified this is a declaration
+ that the method will return a value of that type, e.g.:
+
+ @output(param.Number())
+
+ To override the default name of the output the type may be declared
+ as a keyword argument, e.g.:
+
+ @output(custom_name=param.Number())
+
+ Multiple outputs may be declared using keywords mapping from output name
+ to the type or using tuples of the same format, i.e. these two declarations
+ are equivalent:
+
+ @output(number=param.Number(), string=param.String())
+
+ @output(('number', param.Number()), ('string', param.String()))
+
+ output also accepts Python object types which will be upgraded to
+ a ClassSelector, e.g.:
+
+ @output(int)
+ """
+ if output:
+ outputs = []
+ for i, out in enumerate(output):
+ i = i if len(output) > 1 else None
+ if isinstance(out, tuple) and len(out) == 2 and isinstance(out[0], str):
+ outputs.append(out+(i,))
+ elif isinstance(out, str):
+ outputs.append((out, Parameter(), i))
+ else:
+ outputs.append((None, out, i))
+ elif kw:
+ # (requires keywords to be kept ordered, which was not true in previous versions)
+ outputs = [(name, otype, i if len(kw) > 1 else None)
+ for i, (name, otype) in enumerate(kw.items())]
+ else:
+ outputs = [(None, Parameter(), None)]
+
+ names, processed = [], []
+ for name, otype, i in outputs:
+ if isinstance(otype, type):
+ if issubclass(otype, Parameter):
+ otype = otype()
+ else:
+ from .import ClassSelector
+ otype = ClassSelector(class_=otype)
+ elif isinstance(otype, tuple) and all(isinstance(t, type) for t in otype):
+ from .import ClassSelector
+ otype = ClassSelector(class_=otype)
+ if not isinstance(otype, Parameter):
+ raise ValueError('output type must be declared with a Parameter class, '
+ 'instance or a Python object type.')
+ processed.append((name, otype, i))
+ names.append(name)
+
+ if len(set(names)) != len(names):
+ raise ValueError('When declaring multiple outputs each value '
+ 'must be unique.')
+
+ _dinfo = getattr(func, '_dinfo', {})
+ _dinfo.update({'outputs': processed})
+
+ @wraps(func)
+ def _output(*args,**kw):
+ return func(*args,**kw)
+
+ _output._dinfo = _dinfo
+
+ return _output
+
+
+def _parse_dependency_spec(spec):
+ """
+ Parses param.depends specifications into three components:
+
+ 1. The dotted path to the sub-object
+ 2. The attribute being depended on, i.e. either a parameter or method
+ 3. The parameter attribute being depended on
+ """
+ assert spec.count(":")<=1
+ spec = spec.strip()
+ m = re.match("(?P<path>[^:]*):?(?P<what>.*)", spec)
+ what = m.group('what')
+ path = "."+m.group('path')
+ m = re.match(r"(?P<obj>.*)(\.)(?P<attr>.*)", path)
+ obj = m.group('obj')
+ attr = m.group("attr")
+ return obj or None, attr, what or 'value'
+
+
+def _params_depended_on(minfo, dynamic=True, intermediate=True):
+ """
+ Resolves dependencies declared on a Parameterized method.
+ Dynamic dependencies, i.e. dependencies on sub-objects which may
+ or may not yet be available, are only resolved if dynamic=True.
+ By default intermediate dependencies, i.e. dependencies on the
+ path to a sub-object are returned. For example for a dependency
+ on 'a.b.c' dependencies on 'a' and 'b' are returned as long as
+ intermediate=True.
+
+ Returns lists of concrete dependencies on available parameters
+ and dynamic dependencies specifications which have to resolved
+ if the referenced sub-objects are defined.
+ """
+ deps, dynamic_deps = [], []
+ dinfo = getattr(minfo.method, "_dinfo", {})
+ for d in dinfo.get('dependencies', list(minfo.cls.param)):
+ ddeps, ddynamic_deps = (minfo.cls if minfo.inst is None else minfo.inst).param._spec_to_obj(d, dynamic, intermediate)
+ dynamic_deps += ddynamic_deps
+ for dep in ddeps:
+ if isinstance(dep, PInfo):
+ deps.append(dep)
+ else:
+ method_deps, method_dynamic_deps = _params_depended_on(dep, dynamic, intermediate)
+ deps += method_deps
+ dynamic_deps += method_dynamic_deps
+ return deps, dynamic_deps
+
+
+def _resolve_mcs_deps(obj, resolved, dynamic, intermediate=True):
+ """
+ Resolves constant and dynamic parameter dependencies previously
+ obtained using the _params_depended_on function. Existing resolved
+ dependencies are updated with a supplied parameter instance while
+ dynamic dependencies are resolved if possible.
+ """
+ dependencies = []
+ for dep in resolved:
+ if not issubclass(type(obj), dep.cls):
+ dependencies.append(dep)
+ continue
+ inst = obj if dep.inst is None else dep.inst
+ dep = PInfo(inst=inst, cls=dep.cls, name=dep.name,
+ pobj=inst.param[dep.name], what=dep.what)
+ dependencies.append(dep)
+ for dep in dynamic:
+ subresolved, _ = obj.param._spec_to_obj(dep.spec, intermediate=intermediate)
+ for subdep in subresolved:
+ if isinstance(subdep, PInfo):
+ dependencies.append(subdep)
+ else:
+ dependencies += _params_depended_on(subdep, intermediate=intermediate)[0]
+ return dependencies
+
+
+def _skip_event(*events, **kwargs):
+ """
+ Checks whether a subobject event should be skipped.
+ Returns True if all the values on the new subobject
+ match the values on the previous subobject.
+ """
+ what = kwargs.get('what', 'value')
+ changed = kwargs.get('changed')
+ if changed is None:
+ return False
+ for e in events:
+ for p in changed:
+ if what == 'value':
+ old = Undefined if e.old is None else _getattrr(e.old, p, None)
+ new = Undefined if e.new is None else _getattrr(e.new, p, None)
+ else:
+ old = Undefined if e.old is None else _getattrr(e.old.param[p], what, None)
+ new = Undefined if e.new is None else _getattrr(e.new.param[p], what, None)
+ if not Comparator.is_equal(old, new):
+ return False
+ return True
+
+
+def extract_dependencies(function):
+ """
+ Extract references from a method or function that declares the references.
+ """
+ subparameters = list(function._dinfo['dependencies'])+list(function._dinfo['kw'].values())
+ params = []
+ for p in subparameters:
+ if isinstance(p, str):
+ owner = get_method_owner(function)
+ *subps, p = p.split('.')
+ for subp in subps:
+ owner = getattr(owner, subp, None)
+ if owner is None:
+ raise ValueError('Cannot depend on undefined sub-parameter {p!r}.')
+ if p in owner.param:
+ pobj = owner.param[p]
+ if pobj not in params:
+ params.append(pobj)
+ else:
+ for sp in extract_dependencies(getattr(owner, p)):
+ if sp not in params:
+ params.append(sp)
+ elif p not in params:
+ params.append(p)
+ return params
+
+
+# Two callers at the module top level to support pickling.
+async def _async_caller(*events, what='value', changed=None, callback=None, function=None):
+ if callback:
+ callback(*events)
+ if not _skip_event or not _skip_event(*events, what=what, changed=changed):
+ await function()
+
+
+def _sync_caller(*events, what='value', changed=None, callback=None, function=None):
+ if callback:
+ callback(*events)
+ if not _skip_event(*events, what=what, changed=changed):
+ return function()
+
+
+def _m_caller(self, method_name, what='value', changed=None, callback=None):
+ """
+ Wraps a method call adding support for scheduling a callback
+ before it is executed and skipping events if a subobject has
+ changed but its values have not.
+ """
+ function = getattr(self, method_name)
+ _caller = _async_caller if iscoroutinefunction(function) else _sync_caller
+ caller = partial(_caller, what=what, changed=changed, callback=callback, function=function)
+ caller._watcher_name = method_name
+ return caller
+
+
+def _add_doc(obj, docstring):
+ """Add a docstring to a namedtuple"""
+ obj.__doc__ = docstring
+
+
+PInfo = namedtuple("PInfo", "inst cls name pobj what")
+_add_doc(PInfo,
+ """
+ Object describing something being watched about a Parameter.
+
+ `inst`: Parameterized instance owning the Parameter, or None
+
+ `cls`: Parameterized class owning the Parameter
+
+ `name`: Name of the Parameter being watched
+
+ `pobj`: Parameter object being watched
+
+ `what`: What is being watched on the Parameter (either 'value' or a slot name)
+ """)
+
+MInfo = namedtuple("MInfo", "inst cls name method")
+_add_doc(MInfo,
+ """
+ Object describing a Parameterized method being watched.
+
+ `inst`: Parameterized instance owning the method, or None
+
+ `cls`: Parameterized class owning the method
+
+ `name`: Name of the method being watched
+
+ `method`: bound method of the object being watched
+ """)
+
+DInfo = namedtuple("DInfo", "spec")
+_add_doc(DInfo,
+ """
+ Object describing dynamic dependencies.
+ `spec`: Dependency specification to resolve
+ """)
+
+Event = namedtuple("Event", "what name obj cls old new type")
+_add_doc(Event,
+ """
+ Object representing an event that triggers a Watcher.
+
+ `what`: What is being watched on the Parameter (either value or a slot name)
+
+ `name`: Name of the Parameter that was set or triggered
+
+ `obj`: Parameterized instance owning the watched Parameter, or None
+
+ `cls`: Parameterized class owning the watched Parameter
+
+ `old`: Previous value of the item being watched
+
+ `new`: New value of the item being watched
+
+ `type`: `triggered` if this event was triggered explicitly), `changed` if
+ the item was set and watching for `onlychanged`, `set` if the item was set,
+ or None if type not yet known
+ """)
+
+_Watcher = namedtuple("Watcher", "inst cls fn mode onlychanged parameter_names what queued precedence")
+
+[docs]class Watcher(_Watcher):
+ """
+ Object declaring a callback function to invoke when an Event is
+ triggered on a watched item.
+
+ `inst`: Parameterized instance owning the watched Parameter, or
+ None
+
+ `cls`: Parameterized class owning the watched Parameter
+
+ `fn`: Callback function to invoke when triggered by a watched
+ Parameter
+
+ `mode`: 'args' for param.watch (call `fn` with PInfo object
+ positional args), or 'kwargs' for param.watch_values (call `fn`
+ with <param_name>:<new_value> keywords)
+
+ `onlychanged`: If True, only trigger for actual changes, not
+ setting to the current value
+
+ `parameter_names`: List of Parameters to watch, by name
+
+ `what`: What to watch on the Parameters (either 'value' or a slot
+ name)
+
+ `queued`: Immediately invoke callbacks triggered during processing
+ of an Event (if False), or queue them up for processing
+ later, after this event has been handled (if True)
+
+ `precedence`: A numeric value which determines the precedence of
+ the watcher. Lower precedence values are executed
+ with higher priority.
+ """
+
+ def __new__(cls_, *args, **kwargs):
+ """
+ Allows creating Watcher without explicit precedence value.
+ """
+ values = dict(zip(cls_._fields, args))
+ values.update(kwargs)
+ if 'precedence' not in values:
+ values['precedence'] = 0
+ return super().__new__(cls_, **values)
+
+ def __str__(self):
+ cls = type(self)
+ attrs = ', '.join([f'{f}={getattr(self, f)!r}' for f in cls._fields])
+ return f"{cls.__name__}({attrs})"
+
+
+
+
+class ParameterMetaclass(type):
+ """
+ Metaclass allowing control over creation of Parameter classes.
+ """
+ def __new__(mcs, classname, bases, classdict):
+
+ # store the class's docstring in __classdoc
+ if '__doc__' in classdict:
+ classdict['__classdoc']=classdict['__doc__']
+
+ # when asking for help on Parameter *object*, return the doc slot
+ classdict['__doc__'] = property(attrgetter('doc'))
+
+ # Compute all slots in order, using a dict later turned into a list
+ # as it's the fastest way to get an ordered set in Python
+ all_slots = {}
+ for bcls in set(chain(*(base.__mro__[::-1] for base in bases))):
+ all_slots.update(dict.fromkeys(getattr(bcls, '__slots__', [])))
+
+ # To get the benefit of slots, subclasses must themselves define
+ # __slots__, whether or not they define attributes not present in
+ # the base Parameter class. That's because a subclass will have
+ # a __dict__ unless it also defines __slots__.
+ if '__slots__' not in classdict:
+ classdict['__slots__'] = []
+ else:
+ all_slots.update(dict.fromkeys(classdict['__slots__']))
+
+ classdict['_all_slots_'] = list(all_slots)
+
+ # No special handling for a __dict__ slot; should there be?
+ return type.__new__(mcs, classname, bases, classdict)
+
+ def __getattribute__(mcs,name):
+ if name=='__doc__':
+ # when asking for help on Parameter *class*, return the
+ # stored class docstring
+ return type.__getattribute__(mcs,'__classdoc')
+ else:
+ return type.__getattribute__(mcs,name)
+
+
+class _ParameterBase(metaclass=ParameterMetaclass):
+ """
+ Base Parameter class used to dynamically update the signature of all
+ the Parameters.
+ """
+
+ @classmethod
+ def _modified_slots_defaults(cls):
+ defaults = cls._slot_defaults.copy()
+ defaults['label'] = defaults.pop('_label')
+ return defaults
+
+ @classmethod
+ def __init_subclass__(cls):
+ # _update_signature has been tested against the Parameters available
+ # in Param, we don't want to break the Parameters created elsewhere
+ # so wrapping this in a loose try/except.
+ try:
+ cls._update_signature()
+ except Exception:
+ # The super signature has been changed so we need to get the one
+ # from the class constructor directly.
+ cls.__signature__ = inspect.signature(cls.__init__)
+
+ @classmethod
+ def _update_signature(cls):
+ defaults = cls._modified_slots_defaults()
+ new_parameters = {}
+
+ for i, kls in enumerate(cls.mro()):
+ if kls.__name__.startswith('_'):
+ continue
+ sig = inspect.signature(kls.__init__)
+ for pname, parameter in sig.parameters.items():
+ if pname == 'self':
+ continue
+ if i >= 1 and parameter.default == inspect.Signature.empty:
+ continue
+ if parameter.kind in (inspect.Parameter.VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL):
+ continue
+ if getattr(parameter, 'default', None) is Undefined:
+ if pname not in defaults:
+ raise LookupError(
+ f'Argument {pname!r} of Parameter {cls.__name__!r} has no '
+ 'entry in _slot_defaults.'
+ )
+ default = defaults[pname]
+ if callable(default) and hasattr(default, 'sig'):
+ default = default.sig
+ new_parameter = parameter.replace(default=default)
+ else:
+ new_parameter = parameter
+ if i >= 1:
+ new_parameter = new_parameter.replace(kind=inspect.Parameter.KEYWORD_ONLY)
+ new_parameters.setdefault(pname, new_parameter)
+
+ def _sorter(p):
+ if p.default == inspect.Signature.empty:
+ return 0
+ else:
+ return 1
+
+ new_parameters = sorted(new_parameters.values(), key=_sorter)
+ new_sig = sig.replace(parameters=new_parameters)
+ cls.__signature__ = new_sig
+
+
+[docs]class Parameter(_ParameterBase):
+ """
+ An attribute descriptor for declaring parameters.
+
+ Parameters are a special kind of class attribute. Setting a
+ Parameterized class attribute to be a Parameter instance causes
+ that attribute of the class (and the class's instances) to be
+ treated as a Parameter. This allows special behavior, including
+ dynamically generated parameter values, documentation strings,
+ constant and read-only parameters, and type or range checking at
+ assignment time.
+
+ For example, suppose someone wants to define two new kinds of
+ objects Foo and Bar, such that Bar has a parameter delta, Foo is a
+ subclass of Bar, and Foo has parameters alpha, sigma, and gamma
+ (and delta inherited from Bar). She would begin her class
+ definitions with something like this::
+
+ class Bar(Parameterized):
+ delta = Parameter(default=0.6, doc='The difference between steps.')
+ ...
+ class Foo(Bar):
+ alpha = Parameter(default=0.1, doc='The starting value.')
+ sigma = Parameter(default=0.5, doc='The standard deviation.',
+ constant=True)
+ gamma = Parameter(default=1.0, doc='The ending value.')
+ ...
+
+ Class Foo would then have four parameters, with delta defaulting
+ to 0.6.
+
+ Parameters have several advantages over plain attributes:
+
+ 1. Parameters can be set automatically when an instance is
+ constructed: The default constructor for Foo (and Bar) will
+ accept arbitrary keyword arguments, each of which can be used
+ to specify the value of a Parameter of Foo (or any of Foo's
+ superclasses). E.g., if a script does this::
+
+ myfoo = Foo(alpha=0.5)
+
+ myfoo.alpha will return 0.5, without the Foo constructor
+ needing special code to set alpha.
+
+ If Foo implements its own constructor, keyword arguments will
+ still be accepted if the constructor accepts a dictionary of
+ keyword arguments (as in ``def __init__(self,**params):``), and
+ then each class calls its superclass (as in
+ ``super(Foo,self).__init__(**params)``) so that the
+ Parameterized constructor will process the keywords.
+
+ 2. A Parameterized class need specify only the attributes of a
+ Parameter whose values differ from those declared in
+ superclasses; the other values will be inherited. E.g. if Foo
+ declares::
+
+ delta = Parameter(default=0.2)
+
+ the default value of 0.2 will override the 0.6 inherited from
+ Bar, but the doc will be inherited from Bar.
+
+ 3. The Parameter descriptor class can be subclassed to provide
+ more complex behavior, allowing special types of parameters
+ that, for example, require their values to be numbers in
+ certain ranges, generate their values dynamically from a random
+ distribution, or read their values from a file or other
+ external source.
+
+ 4. The attributes associated with Parameters provide enough
+ information for automatically generating property sheets in
+ graphical user interfaces, allowing Parameterized instances to
+ be edited by users.
+
+ Note that Parameters can only be used when set as class attributes
+ of Parameterized classes. Parameters used as standalone objects,
+ or as class attributes of non-Parameterized classes, will not have
+ the behavior described here.
+ """
+
+ # Because they implement __get__ and __set__, Parameters are known
+ # as 'descriptors' in Python; see "Implementing Descriptors" and
+ # "Invoking Descriptors" in the 'Customizing attribute access'
+ # section of the Python reference manual:
+ # http://docs.python.org/ref/attribute-access.html
+ #
+ # Overview of Parameters for programmers
+ # ======================================
+ #
+ # Consider the following code:
+ #
+ #
+ # class A(Parameterized):
+ # p = Parameter(default=1)
+ #
+ # a1 = A()
+ # a2 = A()
+ #
+ #
+ # * a1 and a2 share one Parameter object (A.__dict__['p']).
+ #
+ # * The default (class) value of p is stored in this Parameter
+ # object (A.__dict__['p'].default).
+ #
+ # * If the value of p is set on a1 (e.g. a1.p=2), a1's value of p
+ # is stored in a1 itself (a1._param__private.values['p'])
+ #
+ # * When a1.p is requested, a1._param__private.values['p'] is
+ # returned. When a2.p is requested, 'p' is not found in
+ # a1._param__private.values, so A.__dict__['p'].default (i.e. A.p) is
+ # returned instead.
+ #
+ #
+ # Be careful when referring to the 'name' of a Parameter:
+ #
+ # * A Parameterized class has a name for the attribute which is
+ # being represented by the Parameter ('p' in the example above);
+ # in the code, this is called the 'name'.
+ #
+ # * When a Parameterized instance has its own local value for a
+ # parameter, it is stored as 'p._param__private.values[X]' where X is the
+ # name of the Parameter
+
+
+ # So that the extra features of Parameters do not require a lot of
+ # overhead, Parameters are implemented using __slots__ (see
+ # http://www.python.org/doc/2.4/ref/slots.html). Instead of having
+ # a full Python dictionary associated with each Parameter instance,
+ # Parameter instances have an enumerated list (named __slots__) of
+ # attributes, and reserve just enough space to store these
+ # attributes. Using __slots__ requires special support for
+ # operations to copy and restore Parameters (e.g. for Python
+ # persistent storage pickling); see __getstate__ and __setstate__.
+ __slots__ = ['name', 'default', 'doc',
+ 'precedence', 'instantiate', 'constant', 'readonly',
+ 'pickle_default_value', 'allow_None', 'per_instance',
+ 'watchers', 'owner', 'allow_refs', 'nested_refs', '_label']
+
+ # Note: When initially created, a Parameter does not know which
+ # Parameterized class owns it, nor does it know its names
+ # (attribute name, internal name). Once the owning Parameterized
+ # class is created, owner, and name are
+ # set.
+
+ _serializers = {'json': serializer.JSONSerialization}
+
+ _slot_defaults = dict(
+ default=None, precedence=None, doc=None, _label=None, instantiate=False,
+ constant=False, readonly=False, pickle_default_value=True, allow_None=False,
+ per_instance=True, allow_refs=False, nested_refs=False
+ )
+
+ # Parameters can be updated during Parameterized class creation when they
+ # are defined multiple times in a class hierarchy. We have to record which
+ # Parameter slots require the default value to be re-validated. Any slots
+ # in this list do not have to trigger such re-validation.
+ _non_validated_slots = ['_label', 'doc', 'name', 'precedence',
+ 'constant', 'pickle_default_value',
+ 'watchers', 'owner']
+
+ @typing.overload
+ def __init__(
+ self,
+ default=None, *,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, doc=Undefined, # pylint: disable-msg=R0913
+ label=Undefined, precedence=Undefined,
+ instantiate=Undefined, constant=Undefined, readonly=Undefined,
+ pickle_default_value=Undefined, allow_None=Undefined,
+ per_instance=Undefined, allow_refs=Undefined, nested_refs=Undefined):
+
+ """Initialize a new Parameter object and store the supplied attributes:
+
+ default: the owning class's value for the attribute represented
+ by this Parameter, which can be overridden in an instance.
+
+ doc: docstring explaining what this parameter represents.
+
+ label: optional text label to be used when this Parameter is
+ shown in a listing. If no label is supplied, the attribute name
+ for this parameter in the owning Parameterized object is used.
+
+ precedence: a numeric value, usually in the range 0.0 to 1.0,
+ which allows the order of Parameters in a class to be defined in
+ a listing or e.g. in GUI menus. A negative precedence indicates
+ a parameter that should be hidden in such listings.
+
+ instantiate: controls whether the value of this Parameter will
+ be deepcopied when a Parameterized object is instantiated (if
+ True), or if the single default value will be shared by all
+ Parameterized instances (if False). For an immutable Parameter
+ value, it is best to leave instantiate at the default of
+ False, so that a user can choose to change the value at the
+ Parameterized instance level (affecting only that instance) or
+ at the Parameterized class or superclass level (affecting all
+ existing and future instances of that class or superclass). For
+ a mutable Parameter value, the default of False is also appropriate
+ if you want all instances to share the same value state, e.g. if
+ they are each simply referring to a single global object like
+ a singleton. If instead each Parameterized should have its own
+ independently mutable value, instantiate should be set to
+ True, but note that there is then no simple way to change the
+ value of this Parameter at the class or superclass level,
+ because each instance, once created, will then have an
+ independently instantiated value.
+
+ constant: if true, the Parameter value can be changed only at
+ the class level or in a Parameterized constructor call. The
+ value is otherwise constant on the Parameterized instance,
+ once it has been constructed.
+
+ readonly: if true, the Parameter value cannot ordinarily be
+ changed by setting the attribute at the class or instance
+ levels at all. The value can still be changed in code by
+ temporarily overriding the value of this slot and then
+ restoring it, which is useful for reporting values that the
+ _user_ should never change but which do change during code
+ execution.
+
+ pickle_default_value: whether the default value should be
+ pickled. Usually, you would want the default value to be pickled,
+ but there are rare cases where that would not be the case (e.g.
+ for file search paths that are specific to a certain system).
+
+ per_instance: whether a separate Parameter instance will be
+ created for every Parameterized instance. True by default.
+ If False, all instances of a Parameterized class will share
+ the same Parameter object, including all validation
+ attributes (bounds, etc.). See also instantiate, which is
+ conceptually similar but affects the Parameter value rather
+ than the Parameter object.
+
+ allow_None: if True, None is accepted as a valid value for
+ this Parameter, in addition to any other values that are
+ allowed. If the default value is defined as None, allow_None
+ is set to True automatically.
+
+ allow_refs: if True allows automatically linking parameter
+ references to this Parameter, i.e. the parameter value will
+ automatically reflect the current value of the reference that
+ is passed in.
+
+ nested_refs: if True and allow_refs=True then even nested objects
+ such as dictionaries, lists, slices, tuples and sets will be
+ inspected for references and will be automatically resolved.
+
+ default, doc, and precedence all default to None, which allows
+ inheritance of Parameter slots (attributes) from the owning-class'
+ class hierarchy (see ParameterizedMetaclass).
+ """
+
+ self.name = None
+ self.owner = None
+ self.allow_refs = allow_refs
+ self.nested_refs = nested_refs
+ self.precedence = precedence
+ self.default = default
+ self.doc = doc
+ self.constant = constant is True or readonly is True # readonly => constant
+ self.readonly = readonly
+ self._label = label
+ self._set_instantiate(instantiate)
+ self.pickle_default_value = pickle_default_value
+ self._set_allow_None(allow_None)
+ self.watchers = {}
+ self.per_instance = per_instance
+
+ @classmethod
+ def serialize(cls, value):
+ "Given the parameter value, return a Python value suitable for serialization"
+ return value
+
+ @classmethod
+ def deserialize(cls, value):
+ "Given a serializable Python value, return a value that the parameter can be set to"
+ return value
+
+ def schema(self, safe=False, subset=None, mode='json'):
+ if serializer is None:
+ raise ImportError('Cannot import serializer.py needed to generate schema')
+ if mode not in self._serializers:
+ raise KeyError(f'Mode {mode!r} not in available serialization formats {list(self._serializers.keys())!r}')
+ return self._serializers[mode].param_schema(self.__class__.__name__, self,
+ safe=safe, subset=subset)
+
+ @property
+ def rx(self):
+ from .reactive import reactive_ops
+ return reactive_ops(self)
+
+ @property
+ def label(self):
+ if self.name and self._label is None:
+ return label_formatter(self.name)
+ else:
+ return self._label
+
+ @label.setter
+ def label(self, val):
+ self._label = val
+
+ def _set_allow_None(self, allow_None):
+ # allow_None is set following these rules (last takes precedence):
+ # 1. to False by default
+ # 2. to the value provided in the constructor, if any
+ # 3. to True if default is None
+ if self.default is None:
+ self.allow_None = True
+ elif allow_None is not Undefined:
+ self.allow_None = allow_None
+ else:
+ self.allow_None = self._slot_defaults['allow_None']
+
+ def _set_instantiate(self,instantiate):
+ """Constant parameters must be instantiated."""
+ # instantiate doesn't actually matter for read-only
+ # parameters, since they can't be set even on a class. But
+ # having this code avoids needless instantiation.
+ if self.readonly:
+ self.instantiate = False
+ elif instantiate is not Undefined:
+ self.instantiate = instantiate
+ else:
+ # Default value
+ self.instantiate = self._slot_defaults['instantiate']
+
+ def __setattr__(self, attribute, value):
+ if attribute == 'name':
+ name = getattr(self, 'name', None)
+ if name is not None and value != name:
+ raise AttributeError("Parameter name cannot be modified after "
+ "it has been bound to a Parameterized.")
+
+ is_slot = attribute in self.__class__._all_slots_
+ has_watcher = attribute != "default" and attribute in getattr(self, 'watchers', [])
+ if not (is_slot or has_watcher):
+ # Return early if attribute is not a slot
+ return super().__setattr__(attribute, value)
+
+ # Otherwise get the old value so we can call watcher/on_set
+ old = getattr(self, attribute, NotImplemented)
+ if is_slot:
+ try:
+ self._on_set(attribute, old, value)
+ except AttributeError:
+ pass
+
+ super().__setattr__(attribute, value)
+ if has_watcher and old is not NotImplemented:
+ self._trigger_event(attribute, old, value)
+
+ def _trigger_event(self, attribute, old, new):
+ event = Event(what=attribute, name=self.name, obj=None, cls=self.owner,
+ old=old, new=new, type=None)
+ for watcher in self.watchers[attribute]:
+ self.owner.param._call_watcher(watcher, event)
+ if not self.owner.param._BATCH_WATCH:
+ self.owner.param._batch_call_watchers()
+
+ def __getattribute__(self, key):
+ """
+ Allow slot values to be Undefined in an "unbound" parameter, i.e. one
+ that is not (yet) owned by a Parameterized object, in which case their
+ value will be retrieved from the _slot_defaults dictionary.
+ """
+ v = object.__getattribute__(self, key)
+ # Safely checks for name (avoiding recursion) to decide if this object is unbound
+ if v is Undefined and key != "name" and getattr(self, "name", None) is None:
+ try:
+ v = self._slot_defaults[key]
+ except KeyError as e:
+ raise KeyError(
+ f'Slot {key!r} on unbound parameter {self.__class__.__name__!r} '
+ 'has no default value defined in `_slot_defaults`'
+ ) from e
+ if callable(v):
+ v = v(self)
+ return v
+
+ def _on_set(self, attribute, old, value):
+ """
+ Can be overridden on subclasses to handle changes when parameter
+ attribute is set.
+ """
+
+ def _update_state(self):
+ """
+ Can be overridden on subclasses to update a Parameter state, i.e. slot
+ values, after the slot values have been set in the inheritance procedure.
+ """
+
+ def __get__(self, obj, objtype): # pylint: disable-msg=W0613
+ """
+ Return the value for this Parameter.
+
+ If called for a Parameterized class, produce that
+ class's value (i.e. this Parameter object's 'default'
+ attribute).
+
+ If called for a Parameterized instance, produce that
+ instance's value, if one has been set - otherwise produce the
+ class's value (default).
+ """
+ if obj is None: # e.g. when __get__ called for a Parameterized class
+ result = self.default
+ else:
+ # Attribute error when .values does not exist (_ClassPrivate)
+ # and KeyError when there's no cached value for this parameter.
+ try:
+ result = obj._param__private.values[self.name]
+ except (AttributeError, KeyError):
+ result = self.default
+ return result
+
+ @instance_descriptor
+ def __set__(self, obj, val):
+ """
+ Set the value for this Parameter.
+
+ If called for a Parameterized class, set that class's
+ value (i.e. set this Parameter object's 'default' attribute).
+
+ If called for a Parameterized instance, set the value of
+ this Parameter on that instance (i.e. in the instance's
+ `values` dictionary located in the private namespace `_param__private`,
+ under the parameter's name).
+
+ If the Parameter's constant attribute is True, only allows
+ the value to be set for a Parameterized class or on
+ uninitialized Parameterized instances.
+
+ If the Parameter's readonly attribute is True, only allows the
+ value to be specified in the Parameter declaration inside the
+ Parameterized source code. A read-only parameter also
+ cannot be set on a Parameterized class.
+
+ Note that until we support some form of read-only
+ object, it is still possible to change the attributes of the
+ object stored in a constant or read-only Parameter (e.g. one
+ item in a list).
+ """
+ name = self.name
+ if obj is not None and self.allow_refs and obj._param__private.initialized and name not in obj._param__private.syncing:
+ ref, deps, val, _ = obj.param._resolve_ref(self, val)
+ refs = obj._param__private.refs
+ if ref is not None:
+ self.owner.param._update_ref(name, ref)
+ elif name in refs:
+ del refs[name]
+
+ # Deprecated Number set_hook called here to avoid duplicating setter
+ if hasattr(self, 'set_hook'):
+ val = self.set_hook(obj, val)
+ if self.set_hook is not _identity_hook:
+ # PARAM3_DEPRECATION
+ warnings.warn(
+ 'Number.set_hook has been deprecated.',
+ category=_ParamDeprecationWarning,
+ stacklevel=6,
+ )
+
+ self._validate(val)
+
+ _old = NotImplemented
+ # obj can be None if __set__ is called for a Parameterized class
+ if self.constant or self.readonly:
+ if self.readonly:
+ raise TypeError("Read-only parameter '%s' cannot be modified" % name)
+ elif obj is None:
+ _old = self.default
+ self.default = val
+ elif not obj._param__private.initialized:
+ _old = obj._param__private.values.get(self.name, self.default)
+ obj._param__private.values[self.name] = val
+ else:
+ _old = obj._param__private.values.get(self.name, self.default)
+ if val is not _old:
+ raise TypeError("Constant parameter '%s' cannot be modified" % name)
+ else:
+ if obj is None:
+ _old = self.default
+ self.default = val
+ else:
+ # When setting a Parameter before calling super.
+ if not isinstance(obj._param__private, _InstancePrivate):
+ obj._param__private = _InstancePrivate()
+ _old = obj._param__private.values.get(name, self.default)
+ obj._param__private.values[name] = val
+ self._post_setter(obj, val)
+
+ if obj is not None:
+ if not hasattr(obj, '_param__private') or not getattr(obj._param__private, 'initialized', False):
+ return
+ obj.param._update_deps(name)
+
+ if obj is None:
+ watchers = self.watchers.get("value")
+ elif name in obj._param__private.watchers:
+ watchers = obj._param__private.watchers[name].get('value')
+ if watchers is None:
+ watchers = self.watchers.get("value")
+ else:
+ watchers = None
+
+ obj = self.owner if obj is None else obj
+
+ if obj is None or not watchers:
+ return
+
+ event = Event(what='value', name=name, obj=obj, cls=self.owner,
+ old=_old, new=val, type=None)
+
+ # Copy watchers here since they may be modified inplace during iteration
+ for watcher in sorted(watchers, key=lambda w: w.precedence):
+ obj.param._call_watcher(watcher, event)
+ if not obj.param._BATCH_WATCH:
+ obj.param._batch_call_watchers()
+
+ def _validate_value(self, value, allow_None):
+ """Implements validation for parameter value"""
+
+ def _validate(self, val):
+ """Implements validation for the parameter value and attributes"""
+ self._validate_value(val, self.allow_None)
+
+ def _post_setter(self, obj, val):
+ """Called after the parameter value has been validated and set"""
+
+ def __delete__(self,obj):
+ raise TypeError("Cannot delete '%s': Parameters deletion not allowed." % self.name)
+
+ def _set_names(self, attrib_name):
+ if None not in (self.owner, self.name) and attrib_name != self.name:
+ raise AttributeError('The {} parameter {!r} has already been '
+ 'assigned a name by the {} class, '
+ 'could not assign new name {!r}. Parameters '
+ 'may not be shared by multiple classes; '
+ 'ensure that you create a new parameter '
+ 'instance for each new class.'.format(type(self).__name__, self.name,
+ self.owner.name, attrib_name))
+ self.name = attrib_name
+
+ def __getstate__(self):
+ """
+ All Parameters have slots, not a dict, so we have to support
+ pickle and deepcopy ourselves.
+ """
+ return {slot: getattr(self, slot) for slot in self.__class__._all_slots_}
+
+ def __setstate__(self,state):
+ # set values of __slots__ (instead of in non-existent __dict__)
+ for k, v in state.items():
+ setattr(self, k, v)
+
+
+# Define one particular type of Parameter that is used in this file
+[docs]class String(Parameter):
+ r"""
+ A String Parameter, with a default value and optional regular expression (regex) matching.
+
+ Example of using a regex to implement IPv4 address matching::
+
+ class IPAddress(String):
+ '''IPv4 address as a string (dotted decimal notation)'''
+ def __init__(self, default="0.0.0.0", allow_None=False, **kwargs):
+ ip_regex = r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
+ super(IPAddress, self).__init__(default=default, regex=ip_regex, **kwargs)
+
+ """
+
+ __slots__ = ['regex']
+
+ _slot_defaults = _dict_update(Parameter._slot_defaults, default="", regex=None)
+
+ @typing.overload
+ def __init__(
+ self,
+ default="", *, regex=None,
+ doc=None, label=None, precedence=None, instantiate=False, constant=False,
+ readonly=False, pickle_default_value=True, allow_None=False, per_instance=True,
+ allow_refs=False, nested_refs=False
+ ):
+ ...
+
+[docs] @_deprecate_positional_args
+ def __init__(self, default=Undefined, *, regex=Undefined, **kwargs):
+ super().__init__(default=default, **kwargs)
+ self.regex = regex
+ self._validate(self.default)
+
+ def _validate_regex(self, val, regex):
+ if (val is None and self.allow_None):
+ return
+ if regex is not None and re.match(regex, val) is None:
+ raise ValueError(
+ f'{_validate_error_prefix(self)} value {val!r} does not '
+ f'match regex {regex!r}.'
+ )
+
+ def _validate_value(self, val, allow_None):
+ if allow_None and val is None:
+ return
+ if not isinstance(val, str):
+ raise ValueError(
+ f'{_validate_error_prefix(self)} only takes a string value, '
+ f'not value of {type(val)}.'
+ )
+
+ def _validate(self, val):
+ self._validate_value(val, self.allow_None)
+ self._validate_regex(val, self.regex)
+
+
+class shared_parameters:
+ """
+ Context manager to share parameter instances when creating
+ multiple Parameterized objects of the same type. Parameter default
+ values are instantiated once and cached to be reused when another
+ Parameterized object of the same type is instantiated.
+ Can be useful to easily modify large collections of Parameterized
+ objects at once and can provide a significant speedup.
+ """
+
+ _share = False
+ _shared_cache = {}
+
+ def __enter__(self):
+ shared_parameters._share = True
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ shared_parameters._share = False
+ shared_parameters._shared_cache = {}
+
+
+def as_uninitialized(fn):
+ """
+ Decorator: call fn with the parameterized_instance's
+ initialization flag set to False, then revert the flag.
+
+ (Used to decorate Parameterized methods that must alter
+ a constant Parameter.)
+ """
+ @wraps(fn)
+ def override_initialization(self_,*args,**kw):
+ parameterized_instance = self_.self
+ original_initialized = parameterized_instance._param__private.initialized
+ parameterized_instance._param__private.initialized = False
+ ret = fn(self_, *args, **kw)
+ parameterized_instance._param__private.initialized = original_initialized
+ return ret
+ return override_initialization
+
+
+class Comparator:
+ """
+ Comparator defines methods for determining whether two objects
+ should be considered equal. It works by registering custom
+ comparison functions, which may either be registed by type or with
+ a predicate function. If no matching comparison can be found for
+ the two objects the comparison will return False.
+
+ If registered by type the Comparator will check whether both
+ objects are of that type and apply the comparison. If the equality
+ function is instead registered with a function it will call the
+ function with each object individually to check if the comparison
+ applies. This is useful for defining comparisons for objects
+ without explicitly importing them.
+
+ To use the Comparator simply call the is_equal function.
+ """
+
+ equalities = {
+ numbers.Number: operator.eq,
+ str: operator.eq,
+ bytes: operator.eq,
+ type(None): operator.eq,
+ lambda o: hasattr(o, '_infinitely_iterable'): operator.eq, # Time
+ }
+ equalities.update({dtt: operator.eq for dtt in dt_types})
+
+ @classmethod
+ def is_equal(cls, obj1, obj2):
+ for eq_type, eq in cls.equalities.items():
+ try:
+ are_instances = isinstance(obj1, eq_type) and isinstance(obj2, eq_type)
+ except TypeError:
+ pass
+ else:
+ if are_instances:
+ return eq(obj1, obj2)
+ if isinstance(eq_type, FunctionType) and eq_type(obj1) and eq_type(obj2):
+ return eq(obj1, obj2)
+ if isinstance(obj2, (list, set, tuple)):
+ return cls.compare_iterator(obj1, obj2)
+ elif isinstance(obj2, dict):
+ return cls.compare_mapping(obj1, obj2)
+ return False
+
+ @classmethod
+ def compare_iterator(cls, obj1, obj2):
+ if type(obj1) != type(obj2) or len(obj1) != len(obj2):
+ return False
+ for o1, o2 in zip(obj1, obj2):
+ if not cls.is_equal(o1, o2):
+ return False
+ return True
+
+ @classmethod
+ def compare_mapping(cls, obj1, obj2):
+ if type(obj1) != type(obj2) or len(obj1) != len(obj2): return False
+ for k in obj1:
+ if k in obj2:
+ if not cls.is_equal(obj1[k], obj2[k]):
+ return False
+ else:
+ return False
+ return True
+
+
+class _ParametersRestorer:
+ """
+ Context-manager to handle the reset of parameter values after an update.
+ """
+
+ def __init__(self, *, parameters, restore):
+ self._parameters = parameters
+ self._restore = restore
+
+ def __enter__(self):
+ return self._restore
+
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ try:
+ self._parameters._update(self._restore)
+ finally:
+ self._restore = {}
+
+
+class Parameters:
+ """Object that holds the namespace and implementation of Parameterized
+ methods as well as any state that is not in __slots__ or the
+ Parameters themselves.
+
+ Exists at both the metaclass level (instantiated by the metaclass)
+ and at the instance level. Can contain state specific to either the
+ class or the instance as necessary.
+ """
+
+ def __init__(self_, cls, self=None):
+ """
+ cls is the Parameterized class which is always set.
+ self is the instance if set.
+ """
+ self_.cls = cls
+ self_.self = self
+
+ @property
+ def _BATCH_WATCH(self_):
+ return self_.self_or_cls._param__private.parameters_state['BATCH_WATCH']
+
+ @_BATCH_WATCH.setter
+ def _BATCH_WATCH(self_, value):
+ self_.self_or_cls._param__private.parameters_state['BATCH_WATCH'] = value
+
+ @property
+ def _TRIGGER(self_):
+ return self_.self_or_cls._param__private.parameters_state['TRIGGER']
+
+ @_TRIGGER.setter
+ def _TRIGGER(self_, value):
+ self_.self_or_cls._param__private.parameters_state['TRIGGER'] = value
+
+ @property
+ def _events(self_):
+ return self_.self_or_cls._param__private.parameters_state['events']
+
+ @_events.setter
+ def _events(self_, value):
+ self_.self_or_cls._param__private.parameters_state['events'] = value
+
+ @property
+ def _state_watchers(self_):
+ return self_.self_or_cls._param__private.parameters_state['watchers']
+
+ @_state_watchers.setter
+ def _state_watchers(self_, value):
+ self_.self_or_cls._param__private.parameters_state['watchers'] = value
+
+ @property
+ def watchers(self_):
+ """Dictionary of instance watchers."""
+ if self_.self is None:
+ raise TypeError('Accessing `.param.watchers` is only supported on a Parameterized instance, not class.')
+ return self_.self._param__private.watchers
+
+ @watchers.setter
+ def watchers(self_, value):
+ if self_.self is None:
+ raise TypeError('Setting `.param.watchers` is only supported on a Parameterized instance, not class.')
+ self_.self._param__private.watchers = value
+
+ @property
+ def self_or_cls(self_):
+ return self_.cls if self_.self is None else self_.self
+
+ def __setstate__(self, state):
+ # Set old parameters state on Parameterized.parameters_state
+ self_, cls = state.get('self'), state.get('cls')
+ self_or_cls = self_ if self_ is not None else cls
+ for k in self_or_cls._param__private.parameters_state:
+ key = '_'+k
+ if key in state:
+ self_or_cls._param__private.parameters_state[k] = state.pop(key)
+ for k, v in state.items():
+ setattr(self, k, v)
+
+ def __getitem__(self_, key):
+ """
+ Returns the class or instance parameter
+ """
+ inst = self_.self
+ params = self_ if inst is None else inst.param
+ p = params.objects(False)[key]
+ return p if inst is None else _instantiated_parameter(inst, p)
+
+ def __dir__(self_):
+ """
+ Adds parameters to dir
+ """
+ return super().__dir__() + list(self_)
+
+ def __iter__(self_):
+ """
+ Iterates over the parameters on this object.
+ """
+ yield from self_.objects(instance=False)
+
+ def __contains__(self_, param):
+ return param in list(self_)
+
+ def __getattr__(self_, attr):
+ """
+ Extends attribute access to parameter objects.
+ """
+ cls = self_.__dict__.get('cls')
+ if cls is None: # Class not initialized
+ raise AttributeError
+
+ params = list(cls._param__private.params)
+ if not params:
+ params = [n for class_ in classlist(cls) for n, v in class_.__dict__.items()
+ if isinstance(v, Parameter)]
+
+ if attr in params:
+ return self_.__getitem__(attr)
+ elif self_.self is None:
+ raise AttributeError(f"type object '{self_.cls.__name__}.param' has no attribute {attr!r}")
+ else:
+ raise AttributeError(f"'{self_.cls.__name__}.param' object has no attribute {attr!r}")
+
+ @as_uninitialized
+ def _set_name(self_, name):
+ self_.self.name = name
+
+ @as_uninitialized
+ def _generate_name(self_):
+ self_._set_name('%s%05d' % (self_.cls.__name__, object_count))
+
+ @as_uninitialized
+ def _setup_params(self_, **params):
+ """
+ Initialize default and keyword parameter values.
+
+ First, ensures that values for all Parameters with 'instantiate=True'
+ (typically used for mutable Parameters) are copied directly into each object,
+ to ensure that there is an independent copy of the value (to avoid surprising
+ aliasing errors). Second, ensures that Parameters with 'constant=True' are
+ referenced on the instance, to make sure that setting a constant
+ Parameter on the class doesn't affect already created instances. Then
+ sets each of the keyword arguments, raising when any of them are not
+ defined as parameters.
+ """
+ self = self_.self
+ ## Deepcopy all 'instantiate=True' parameters
+ params_to_deepcopy = {}
+ params_to_ref = {}
+ for pname, p in self_.objects(instance=False).items():
+ if p.instantiate and pname != "name":
+ params_to_deepcopy[pname] = p
+ elif p.constant and pname != 'name':
+ params_to_ref[pname] = p
+
+ for p in params_to_deepcopy.values():
+ self_._instantiate_param(p)
+ for p in params_to_ref.values():
+ self_._instantiate_param(p, deepcopy=False)
+
+ ## keyword arg setting
+ deps, refs = {}, {}
+ objects = self.param.objects(instance=False)
+ for name, val in params.items():
+ desc = self_.cls.get_param_descriptor(name)[0] # pylint: disable-msg=E1101
+ if not desc:
+ raise TypeError(
+ f"{self.__class__.__name__}.__init__() got an unexpected "
+ f"keyword argument {name!r}"
+ )
+
+ pobj = objects.get(name)
+ if pobj is None or not pobj.allow_refs:
+ # Until Parameter.allow_refs=True by default we have to
+ # speculatively evaluate a values to check whether they
+ # contain a reference and warn the user that the
+ # behavior may change in future.
+ if name not in self_.cls._param__private.explicit_no_refs:
+ try:
+ ref, _, resolved, _ = self_._resolve_ref(pobj, val)
+ except Exception:
+ ref = None
+ if ref:
+ warnings.warn(
+ f"Parameter {name!r} on {pobj.owner} is being given a valid parameter "
+ f"reference {val} but is implicitly allow_refs=False. "
+ "In future allow_refs will be enabled by default and "
+ f"the reference {val} will be resolved to its underlying "
+ f"value {resolved}. Please explicitly set allow_ref on the "
+ "Parameter definition to declare whether references "
+ "should be resolved or not.",
+ category=_ParamFutureWarning,
+ stacklevel=4,
+ )
+ setattr(self, name, val)
+ continue
+
+ # Resolve references
+ ref, ref_deps, resolved, is_async = self_._resolve_ref(pobj, val)
+ if ref is not None:
+ refs[name] = ref
+ deps[name] = ref_deps
+ if not is_async:
+ setattr(self, name, resolved)
+ return refs, deps
+
+ def _setup_refs(self_, refs):
+ groups = defaultdict(list)
+ for pname, subrefs in refs.items():
+ for p in subrefs:
+
+ if isinstance(p, Parameter):
+ groups[p.owner].append((pname, p.name))
+ else:
+ for sp in extract_dependencies(p):
+ groups[sp.owner].append((pname, sp.name))
+ for owner, pnames in groups.items():
+ refnames, pnames = zip(*pnames)
+ self_.self._param__private.ref_watchers.append((
+ refnames,
+ owner.param._watch(self_._sync_refs, list(set(pnames)), precedence=-1)
+ ))
+
+ def _update_ref(self_, name, ref):
+ for _, watcher in self_.self._param__private.ref_watchers:
+ dep_obj = watcher.cls if watcher.inst is None else watcher.inst
+ dep_obj.param.unwatch(watcher)
+ self_.self._param__private.ref_watchers = []
+ refs = dict(self_.self._param__private.refs, **{name: ref})
+ deps = {name: resolve_ref(ref) for name, ref in refs.items()}
+ self_._setup_refs(deps)
+ self_.self._param__private.refs = refs
+
+ def _sync_refs(self_, *events):
+ updates = {}
+ for pname, ref in self_.self._param__private.refs.items():
+ # Skip updating value if dependency has not changed
+ deps = resolve_ref(ref, self_[pname].nested_refs)
+ is_async = iscoroutinefunction(ref)
+ if not any((dep.owner is e.obj and dep.name == e.name) for dep in deps for e in events) and not is_async:
+ continue
+
+ new_val = resolve_value(ref)
+ if is_async:
+ async_executor(partial(self_._async_ref, pname, new_val))
+ continue
+
+ updates[pname] = new_val
+
+ with edit_constant(self_.self):
+ with _syncing(self_.self, updates):
+ self_.update(updates)
+
+ def _resolve_ref(self_, pobj, value):
+ is_async = iscoroutinefunction(value)
+ deps = resolve_ref(value, recursive=pobj.nested_refs)
+ if not deps and not is_async:
+ return None, None, value, False
+ ref = value
+ value = resolve_value(value)
+ if is_async:
+ async_executor(partial(self_._async_ref, pobj.name, value))
+ value = None
+ return ref, deps, value, is_async
+
+ async def _async_ref(self_, pname, awaitable):
+ if not self_.self._param__private.initialized:
+ async_executor(partial(self_._async_ref, pname, awaitable))
+ return
+ if pname in self_.self._param__private.async_refs:
+ self_.self._param__private.async_refs[pname].cancel()
+ self_.self._param__private.async_refs[pname] = asyncio.current_task()
+ try:
+ if isinstance(awaitable, types.AsyncGeneratorType):
+ async for new_obj in awaitable:
+ with _syncing(self_.self, (pname,)):
+ self_.update({pname: new_obj})
+ else:
+ with _syncing(self_.self, (pname,)):
+ self_.update({pname: await awaitable})
+ except Exception as e:
+ raise e
+ finally:
+ del self_.self._param__private.async_refs[pname]
+
+ @classmethod
+ def _changed(cls, event):
+ """
+ Predicate that determines whether a Event object has actually
+ changed such that old != new.
+ """
+ return not Comparator.is_equal(event.old, event.new)
+
+ def _instantiate_param(self_, param_obj, dict_=None, key=None, deepcopy=True):
+ # deepcopy or store a reference to reference param_obj.default into
+ # self._param__private.values (or dict_ if supplied) under the
+ # parameter's name (or key if supplied)
+ instantiator = copy.deepcopy if deepcopy else lambda o: o
+ self = self_.self
+ dict_ = dict_ or self._param__private.values
+ key = key or param_obj.name
+ if shared_parameters._share:
+ param_key = (str(type(self)), param_obj.name)
+ if param_key in shared_parameters._shared_cache:
+ new_object = shared_parameters._shared_cache[param_key]
+ else:
+ new_object = instantiator(param_obj.default)
+ shared_parameters._shared_cache[param_key] = new_object
+ else:
+ new_object = instantiator(param_obj.default)
+
+ dict_[key] = new_object
+
+ if isinstance(new_object, Parameterized):
+ global object_count
+ object_count += 1
+ # Writes over name given to the original object;
+ # could instead have kept the same name
+ new_object.param._generate_name()
+
+ def _update_deps(self_, attribute=None, init=False):
+ obj = self_.self
+ init_methods = []
+ for method, queued, on_init, constant, dynamic in type(obj).param._depends['watch']:
+ # On initialization set up constant watchers; otherwise
+ # clean up previous dynamic watchers for the updated attribute
+ dynamic = [d for d in dynamic if attribute is None or d.spec.split(".")[0] == attribute]
+ if init:
+ constant_grouped = defaultdict(list)
+ for dep in _resolve_mcs_deps(obj, constant, []):
+ constant_grouped[(id(dep.inst), id(dep.cls), dep.what)].append((None, dep))
+ for group in constant_grouped.values():
+ self_._watch_group(obj, method, queued, group)
+ m = getattr(self_.self, method)
+ if on_init and m not in init_methods:
+ init_methods.append(m)
+ elif dynamic:
+ for w in obj._param__private.dynamic_watchers.pop(method, []):
+ (w.cls if w.inst is None else w.inst).param.unwatch(w)
+ else:
+ continue
+
+ # Resolve dynamic dependencies one-by-one to be able to trace their watchers
+ grouped = defaultdict(list)
+ for ddep in dynamic:
+ for dep in _resolve_mcs_deps(obj, [], [ddep]):
+ grouped[(id(dep.inst), id(dep.cls), dep.what)].append((ddep, dep))
+
+ for group in grouped.values():
+ watcher = self_._watch_group(obj, method, queued, group, attribute)
+ obj._param__private.dynamic_watchers[method].append(watcher)
+ for m in init_methods:
+ m()
+
+ def _resolve_dynamic_deps(self, obj, dynamic_dep, param_dep, attribute):
+ """
+ If a subobject whose parameters are being depended on changes
+ we should only trigger events if the actual parameter values
+ of the new object differ from those on the old subobject,
+ therefore we accumulate parameters to compare on a subobject
+ change event.
+
+ Additionally we need to make sure to notify the parent object
+ if a subobject changes so the dependencies can be
+ reinitialized so we return a callback which updates the
+ dependencies.
+ """
+ subobj = obj
+ subobjs = [obj]
+ for subpath in dynamic_dep.spec.split('.')[:-1]:
+ subobj = getattr(subobj, subpath.split(':')[0], None)
+ subobjs.append(subobj)
+
+ dep_obj = param_dep.cls if param_dep.inst is None else param_dep.inst
+ if dep_obj not in subobjs[:-1]:
+ return None, None, param_dep.what
+
+ depth = subobjs.index(dep_obj)
+ callback = None
+ if depth > 0:
+ def callback(*events):
+ """
+ If a subobject changes, we need to notify the main
+ object to update the dependencies.
+ """
+ obj.param._update_deps(attribute)
+
+ p = '.'.join(dynamic_dep.spec.split(':')[0].split('.')[depth+1:])
+ if p == 'param':
+ subparams = [sp for sp in list(subobjs[-1].param)]
+ else:
+ subparams = [p]
+
+ if ':' in dynamic_dep.spec:
+ what = dynamic_dep.spec.split(':')[-1]
+ else:
+ what = param_dep.what
+
+ return subparams, callback, what
+
+ def _watch_group(self_, obj, name, queued, group, attribute=None):
+ """
+ Sets up a watcher for a group of dependencies. Ensures that
+ if the dependency was dynamically generated we check whether
+ a subobject change event actually causes a value change and
+ that we update the existing watchers, i.e. clean up watchers
+ on the old subobject and create watchers on the new subobject.
+ """
+ dynamic_dep, param_dep = group[0]
+ dep_obj = param_dep.cls if param_dep.inst is None else param_dep.inst
+ params = []
+ for _, g in group:
+ if g.name not in params:
+ params.append(g.name)
+
+ if dynamic_dep is None:
+ subparams, callback, what = None, None, param_dep.what
+ else:
+ subparams, callback, what = self_._resolve_dynamic_deps(
+ obj, dynamic_dep, param_dep, attribute)
+
+ mcaller = _m_caller(obj, name, what, subparams, callback)
+ return dep_obj.param._watch(
+ mcaller, params, param_dep.what, queued=queued, precedence=-1)
+
+ @_recursive_repr()
+ def _repr_html_(self_, open=True):
+ return _parameterized_repr_html(self_.self_or_cls, open)
+
+ # Classmethods
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="""Use instead `for k,v in p.param.objects().items(): print(f"{p.__class__.name}.{k}={repr(v.default)}")`""")
+ def print_param_defaults(self_):
+ """Print the default values of all cls's Parameters.
+
+ .. deprecated:: 1.12.0
+ Use instead `for k,v in p.param.objects().items(): print(f"{p.__class__.name}.{k}={repr(v.default)}")`
+ """
+ cls = self_.cls
+ for key,val in cls.__dict__.items():
+ if isinstance(val,Parameter):
+ print(cls.__name__+'.'+key+ '='+ repr(val.default))
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `p.param.default =`")
+ def set_default(self_,param_name,value):
+ """
+ Set the default value of param_name.
+
+ Equivalent to setting param_name on the class.
+
+ .. deprecated:: 1.12.0
+ Use instead `p.param.default =`
+ """
+ cls = self_.cls
+ setattr(cls,param_name,value)
+
+[docs] def add_parameter(self_, param_name, param_obj):
+ """
+ Add a new Parameter object into this object's class.
+
+ Should result in a Parameter equivalent to one declared
+ in the class's source code.
+ """
+ # Could have just done setattr(cls,param_name,param_obj),
+ # which is supported by the metaclass's __setattr__ , but
+ # would need to handle the params() cache as well
+ # (which is tricky but important for startup speed).
+ cls = self_.cls
+ type.__setattr__(cls,param_name,param_obj)
+ ParameterizedMetaclass._initialize_parameter(cls,param_name,param_obj)
+ # delete cached params()
+ cls._param__private.params.clear()
+
+ # PARAM3_DEPRECATION
+ @_deprecated(extra_msg="Use instead `.param.add_parameter`")
+ def _add_parameter(self_,param_name, param_obj):
+ """Add a new Parameter object into this object's class.
+
+ .. deprecated :: 1.12.0
+ """
+ return self_.add_parameter(param_name, param_obj)
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `.param.values()` or `.param['param']`")
+ def params(self_, parameter_name=None):
+ """
+ Return the Parameters of this class as the
+ dictionary {name: parameter_object}
+
+ Includes Parameters from this class and its
+ superclasses.
+
+ .. deprecated:: 1.12.0
+ Use instead `.param.values()` or `.param['param']`
+ """
+ pdict = self_.objects(instance='existing')
+ if parameter_name is None:
+ return pdict
+ else:
+ return pdict[parameter_name]
+
+ # Bothmethods
+
+[docs] def update(self_, *args, **kwargs):
+ """
+ For the given dictionary or iterable or set of param=value
+ keyword arguments, sets the corresponding parameter of this
+ object or class to the given value.
+
+ May also be used as a context manager to temporarily set and
+ then reset parameter values.
+ """
+ restore = self_._update(*args, **kwargs)
+ return _ParametersRestorer(parameters=self_, restore=restore)
+
+ def _update(self_, *args, **kwargs):
+ BATCH_WATCH = self_._BATCH_WATCH
+ self_._BATCH_WATCH = True
+ self_or_cls = self_.self_or_cls
+ if args:
+ if len(args) == 1 and not kwargs:
+ kwargs = args[0]
+ else:
+ self_._BATCH_WATCH = False
+ raise ValueError(
+ f"{self_.cls.__name__}.param.update accepts *either* an iterable "
+ "or key=value pairs, not both."
+ )
+
+ trigger_params = [
+ k for k in kwargs
+ if k in self_ and hasattr(self_[k], '_autotrigger_value')
+ ]
+
+ for tp in trigger_params:
+ self_[tp]._mode = 'set'
+
+ values = self_.values()
+ restore = {k: values[k] for k, v in kwargs.items() if k in values}
+
+ for (k, v) in kwargs.items():
+ if k not in self_:
+ self_._BATCH_WATCH = False
+ raise ValueError(f"{k!r} is not a parameter of {self_.cls.__name__}")
+ try:
+ setattr(self_or_cls, k, v)
+ except:
+ self_._BATCH_WATCH = False
+ raise
+
+ self_._BATCH_WATCH = BATCH_WATCH
+ if not BATCH_WATCH:
+ self_._batch_call_watchers()
+
+ for tp in trigger_params:
+ p = self_[tp]
+ p._mode = 'reset'
+ setattr(self_or_cls, tp, p._autotrigger_reset_value)
+ p._mode = 'set-reset'
+ return restore
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `.param.update`")
+ def set_param(self_, *args,**kwargs):
+ """
+ For each param=value keyword argument, sets the corresponding
+ parameter of this object or class to the given value.
+
+ For backwards compatibility, also accepts
+ set_param("param",value) for a single parameter value using
+ positional arguments, but the keyword interface is preferred
+ because it is more compact and can set multiple values.
+
+ .. deprecated:: 1.12.0
+ Use instead `.param.update`
+ """
+ self_or_cls = self_.self_or_cls
+ if args:
+ if len(args) == 2 and not args[0] in kwargs and not kwargs:
+ kwargs[args[0]] = args[1]
+ else:
+ raise ValueError("Invalid positional arguments for %s.set_param" %
+ (self_or_cls.name))
+ return self_.update(kwargs)
+
+[docs] def objects(self_, instance=True):
+ """
+ Returns the Parameters of this instance or class
+
+ If instance=True and called on a Parameterized instance it
+ will create instance parameters for all Parameters defined on
+ the class. To force class parameters to be returned use
+ instance=False. Since classes avoid creating instance
+ parameters unless necessary you may also request only existing
+ instance parameters to be returned by setting
+ instance='existing'.
+ """
+ if self_.self is not None and not self_.self._param__private.initialized and instance is True:
+ warnings.warn(
+ 'Looking up instance Parameter objects (`.param.objects()`) until '
+ 'the Parameterized instance has been fully initialized is deprecated and will raise an error in a future version. '
+ 'Ensure you have called `super().__init__(**params)` in your Parameterized '
+ 'constructor before trying to access instance Parameter objects, or '
+ 'looking up the class Parameter objects with `.param.objects(instance=False)` '
+ 'may be enough for your use case.',
+ category=_ParamFutureWarning,
+ stacklevel=2,
+ )
+
+ cls = self_.cls
+ # We cache the parameters because this method is called often,
+ # and parameters are rarely added (and cannot be deleted)
+ pdict = cls._param__private.params
+ if not pdict:
+ paramdict = {}
+ for class_ in classlist(cls):
+ for name, val in class_.__dict__.items():
+ if isinstance(val, Parameter):
+ paramdict[name] = val
+
+ # We only want the cache to be visible to the cls on which
+ # params() is called, so we mangle the name ourselves at
+ # runtime (if we were to mangle it now, it would be
+ # _Parameterized.__params for all classes).
+ # cls._param__private.params[f'_{cls.__name__}__params'] = paramdict
+ cls._param__private.params = paramdict
+ pdict = paramdict
+
+ if instance and self_.self is not None:
+ if instance == 'existing':
+ if getattr(self_.self._param__private, 'initialized', False) and self_.self._param__private.params:
+ return dict(pdict, **self_.self._param__private.params)
+ return pdict
+ else:
+ return {k: self_.self.param[k] for k in pdict}
+ return pdict
+
+[docs] def trigger(self_, *param_names):
+ """
+ Trigger watchers for the given set of parameter names. Watchers
+ will be triggered whether or not the parameter values have
+ actually changed. As a special case, the value will actually be
+ changed for a Parameter of type Event, setting it to True so
+ that it is clear which Event parameter has been triggered.
+ """
+ if self_.self is not None and not self_.self._param__private.initialized:
+ warnings.warn(
+ 'Triggering watchers on a partially initialized Parameterized instance '
+ 'is deprecated and will raise an error in a future version. '
+ 'Ensure you have called super().__init__(**params) in '
+ 'the Parameterized instance constructor before trying to set up a watcher.',
+ category=_ParamFutureWarning,
+ stacklevel=2,
+ )
+
+ trigger_params = [p for p in self_
+ if hasattr(self_[p], '_autotrigger_value')]
+ triggers = {p:self_[p]._autotrigger_value
+ for p in trigger_params if p in param_names}
+
+ events = self_._events
+ watchers = self_._state_watchers
+ self_._events = []
+ self_._state_watchers = []
+ param_values = self_.values()
+ params = {name: param_values[name] for name in param_names}
+ self_._TRIGGER = True
+ self_.update(dict(params, **triggers))
+ self_._TRIGGER = False
+ self_._events += events
+ self_._state_watchers += watchers
+
+ def _update_event_type(self_, watcher, event, triggered):
+ """
+ Returns an updated Event object with the type field set appropriately.
+ """
+ if triggered:
+ event_type = 'triggered'
+ else:
+ event_type = 'changed' if watcher.onlychanged else 'set'
+ return Event(what=event.what, name=event.name, obj=event.obj, cls=event.cls,
+ old=event.old, new=event.new, type=event_type)
+
+ def _execute_watcher(self, watcher, events):
+ if watcher.mode == 'args':
+ args, kwargs = events, {}
+ else:
+ args, kwargs = (), {event.name: event.new for event in events}
+
+ if iscoroutinefunction(watcher.fn):
+ if async_executor is None:
+ raise RuntimeError("Could not execute %s coroutine function. "
+ "Please register a asynchronous executor on "
+ "param.parameterized.async_executor, which "
+ "schedules the function on an event loop." %
+ watcher.fn)
+ async_executor(partial(watcher.fn, *args, **kwargs))
+ else:
+ watcher.fn(*args, **kwargs)
+
+ def _call_watcher(self_, watcher, event):
+ """
+ Invoke the given watcher appropriately given an Event object.
+ """
+ if self_._TRIGGER:
+ pass
+ elif watcher.onlychanged and (not self_._changed(event)):
+ return
+
+ if self_._BATCH_WATCH:
+ self_._events.append(event)
+ if not any(watcher is w for w in self_._state_watchers):
+ self_._state_watchers.append(watcher)
+ else:
+ event = self_._update_event_type(watcher, event, self_._TRIGGER)
+ with _batch_call_watchers(self_.self_or_cls, enable=watcher.queued, run=False):
+ self_._execute_watcher(watcher, (event,))
+
+ def _batch_call_watchers(self_):
+ """
+ Batch call a set of watchers based on the parameter value
+ settings in kwargs using the queued Event and watcher objects.
+ """
+ while self_._events:
+ event_dict = OrderedDict([((event.name, event.what), event)
+ for event in self_._events])
+ watchers = self_._state_watchers[:]
+ self_._events = []
+ self_._state_watchers = []
+
+ for watcher in sorted(watchers, key=lambda w: w.precedence):
+ events = [self_._update_event_type(watcher, event_dict[(name, watcher.what)],
+ self_._TRIGGER)
+ for name in watcher.parameter_names
+ if (name, watcher.what) in event_dict]
+ with _batch_call_watchers(self_.self_or_cls, enable=watcher.queued, run=False):
+ self_._execute_watcher(watcher, events)
+
+[docs] def set_dynamic_time_fn(self_,time_fn,sublistattr=None):
+ """
+ Set time_fn for all Dynamic Parameters of this class or
+ instance object that are currently being dynamically
+ generated.
+
+ Additionally, sets _Dynamic_time_fn=time_fn on this class or
+ instance object, so that any future changes to Dynamic
+ Parmeters can inherit time_fn (e.g. if a Number is changed
+ from a float to a number generator, the number generator will
+ inherit time_fn).
+
+ If specified, sublistattr is the name of an attribute of this
+ class or instance that contains an iterable collection of
+ subobjects on which set_dynamic_time_fn should be called. If
+ the attribute sublistattr is present on any of the subobjects,
+ set_dynamic_time_fn() will be called for those, too.
+ """
+ self_or_cls = self_.self_or_cls
+ self_or_cls._Dynamic_time_fn = time_fn
+
+ if isinstance(self_or_cls,type):
+ a = (None,self_or_cls)
+ else:
+ a = (self_or_cls,)
+
+ for n,p in self_or_cls.param.objects('existing').items():
+ if hasattr(p, '_value_is_dynamic'):
+ if p._value_is_dynamic(*a):
+ g = self_or_cls.param.get_value_generator(n)
+ g._Dynamic_time_fn = time_fn
+
+ if sublistattr:
+ try:
+ sublist = getattr(self_or_cls,sublistattr)
+ except AttributeError:
+ sublist = []
+
+ for obj in sublist:
+ obj.param.set_dynamic_time_fn(time_fn,sublistattr)
+
+[docs] def serialize_parameters(self_, subset=None, mode='json'):
+ self_or_cls = self_.self_or_cls
+ if mode not in Parameter._serializers:
+ raise ValueError(f'Mode {mode!r} not in available serialization formats {list(Parameter._serializers.keys())!r}')
+ serializer = Parameter._serializers[mode]
+ return serializer.serialize_parameters(self_or_cls, subset=subset)
+
+[docs] def serialize_value(self_, pname, mode='json'):
+ self_or_cls = self_.self_or_cls
+ if mode not in Parameter._serializers:
+ raise ValueError(f'Mode {mode!r} not in available serialization formats {list(Parameter._serializers.keys())!r}')
+ serializer = Parameter._serializers[mode]
+ return serializer.serialize_parameter_value(self_or_cls, pname)
+
+[docs] def deserialize_parameters(self_, serialization, subset=None, mode='json'):
+ self_or_cls = self_.self_or_cls
+ serializer = Parameter._serializers[mode]
+ return serializer.deserialize_parameters(self_or_cls, serialization, subset=subset)
+
+[docs] def deserialize_value(self_, pname, value, mode='json'):
+ self_or_cls = self_.self_or_cls
+ if mode not in Parameter._serializers:
+ raise ValueError(f'Mode {mode!r} not in available serialization formats {list(Parameter._serializers.keys())!r}')
+ serializer = Parameter._serializers[mode]
+ return serializer.deserialize_parameter_value(self_or_cls, pname, value)
+
+[docs] def schema(self_, safe=False, subset=None, mode='json'):
+ """
+ Returns a schema for the parameters on this Parameterized object.
+ """
+ self_or_cls = self_.self_or_cls
+ if mode not in Parameter._serializers:
+ raise ValueError(f'Mode {mode!r} not in available serialization formats {list(Parameter._serializers.keys())!r}')
+ serializer = Parameter._serializers[mode]
+ return serializer.schema(self_or_cls, safe=safe, subset=subset)
+
+ # PARAM3_DEPRECATION
+ # same as values() but returns list, not dict
+[docs] @_deprecated(extra_msg="""
+ Use `.param.values().items()` instead (or `.param.values()` for the
+ common case of `dict(....param.get_param_values())`)
+ """)
+ def get_param_values(self_, onlychanged=False):
+ """
+ Return a list of name,value pairs for all Parameters of this
+ object.
+
+ When called on an instance with onlychanged set to True, will
+ only return values that are not equal to the default value
+ (onlychanged has no effect when called on a class).
+
+ .. deprecated:: 1.12.0
+ Use `.param.values().items()` instead (or `.param.values()` for the
+ common case of `dict(....param.get_param_values())`)
+ """
+ vals = self_.values(onlychanged)
+ return [(k, v) for k, v in vals.items()]
+
+[docs] def values(self_, onlychanged=False):
+ """
+ Return a dictionary of name,value pairs for the Parameters of this
+ object.
+
+ When called on an instance with onlychanged set to True, will
+ only return values that are not equal to the default value
+ (onlychanged has no effect when called on a class).
+ """
+ self_or_cls = self_.self_or_cls
+ vals = []
+ for name, val in self_or_cls.param.objects('existing').items():
+ value = self_or_cls.param.get_value_generator(name)
+ if name == 'name' and onlychanged and _is_auto_name(self_.cls.__name__, value):
+ continue
+ if not onlychanged or not Comparator.is_equal(value, val.default):
+ vals.append((name, value))
+
+ vals.sort(key=itemgetter(0))
+ return dict(vals)
+
+[docs] def force_new_dynamic_value(self_, name): # pylint: disable-msg=E0213
+ """
+ Force a new value to be generated for the dynamic attribute
+ name, and return it.
+
+ If name is not dynamic, its current value is returned
+ (i.e. equivalent to getattr(name).
+ """
+ cls_or_slf = self_.self_or_cls
+ param_obj = cls_or_slf.param.objects('existing').get(name)
+
+ if not param_obj:
+ return getattr(cls_or_slf, name)
+
+ cls, slf = None, None
+ if isinstance(cls_or_slf,type):
+ cls = cls_or_slf
+ else:
+ slf = cls_or_slf
+
+ if not hasattr(param_obj,'_force'):
+ return param_obj.__get__(slf, cls)
+ else:
+ return param_obj._force(slf, cls)
+
+[docs] def get_value_generator(self_,name): # pylint: disable-msg=E0213
+ """
+ Return the value or value-generating object of the named
+ attribute.
+
+ For most parameters, this is simply the parameter's value
+ (i.e. the same as getattr()), but Dynamic parameters have
+ their value-generating object returned.
+ """
+ cls_or_slf = self_.self_or_cls
+ param_obj = cls_or_slf.param.objects('existing').get(name)
+
+ if not param_obj:
+ value = getattr(cls_or_slf,name)
+
+ # CompositeParameter detected by being a Parameter and having 'attribs'
+ elif hasattr(param_obj,'attribs'):
+ value = [cls_or_slf.param.get_value_generator(a) for a in param_obj.attribs]
+
+ # not a Dynamic Parameter
+ elif not hasattr(param_obj,'_value_is_dynamic'):
+ value = getattr(cls_or_slf,name)
+
+ # Dynamic Parameter...
+ else:
+ # TODO: is this always an instance?
+ if isinstance(cls_or_slf, Parameterized) and name in cls_or_slf._param__private.values:
+ # dealing with object and it's been set on this object
+ value = cls_or_slf._param__private.values[name]
+ else:
+ # dealing with class or isn't set on the object
+ value = param_obj.default
+
+ return value
+
+[docs] def inspect_value(self_,name): # pylint: disable-msg=E0213
+ """
+ Return the current value of the named attribute without modifying it.
+
+ Same as getattr() except for Dynamic parameters, which have their
+ last generated value returned.
+ """
+ cls_or_slf = self_.self_or_cls
+ param_obj = cls_or_slf.param.objects('existing').get(name)
+
+ if not param_obj:
+ value = getattr(cls_or_slf,name)
+ elif hasattr(param_obj,'attribs'):
+ value = [cls_or_slf.param.inspect_value(a) for a in param_obj.attribs]
+ elif not hasattr(param_obj,'_inspect'):
+ value = getattr(cls_or_slf,name)
+ else:
+ if isinstance(cls_or_slf,type):
+ value = param_obj._inspect(None,cls_or_slf)
+ else:
+ value = param_obj._inspect(cls_or_slf,None)
+
+ return value
+
+[docs] def method_dependencies(self_, name, intermediate=False):
+ """
+ Given the name of a method, returns a PInfo object for each dependency
+ of this method. See help(PInfo) for the contents of these objects.
+
+ By default intermediate dependencies on sub-objects are not
+ returned as these are primarily useful for internal use to
+ determine when a sub-object dependency has to be updated.
+ """
+ method = getattr(self_.self_or_cls, name)
+ minfo = MInfo(cls=self_.cls, inst=self_.self, name=name,
+ method=method)
+ deps, dynamic = _params_depended_on(
+ minfo, dynamic=False, intermediate=intermediate)
+ if self_.self is None:
+ return deps
+ return _resolve_mcs_deps(
+ self_.self, deps, dynamic, intermediate=intermediate)
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg='Use instead `.param.method_dependencies`')
+ def params_depended_on(self_, *args, **kwargs):
+ """
+ Given the name of a method, returns a PInfo object for each dependency
+ of this method. See help(PInfo) for the contents of these objects.
+
+ By default intermediate dependencies on sub-objects are not
+ returned as these are primarily useful for internal use to
+ determine when a sub-object dependency has to be updated.
+
+ .. deprecated: 2.0.0
+ Use instead `.param.method_dependencies`
+ """
+ return self_.method_dependencies(*args, **kwargs)
+
+[docs] def outputs(self_):
+ """
+ Returns a mapping between any declared outputs and a tuple
+ of the declared Parameter type, the output method, and the
+ index into the output if multiple outputs are returned.
+ """
+ outputs = {}
+ for cls in classlist(self_.cls):
+ for name in dir(cls):
+ if name == '_param_watchers':
+ continue
+ method = getattr(self_.self_or_cls, name)
+ dinfo = getattr(method, '_dinfo', {})
+ if 'outputs' not in dinfo:
+ continue
+ for override, otype, idx in dinfo['outputs']:
+ if override is not None:
+ name = override
+ outputs[name] = (otype, method, idx)
+ return outputs
+
+ def _spec_to_obj(self_, spec, dynamic=True, intermediate=True):
+ """
+ Resolves a dependency specification into lists of explicit
+ parameter dependencies and dynamic dependencies.
+
+ Dynamic dependencies are specifications to be resolved when
+ the sub-object whose parameters are being depended on is
+ defined.
+
+ During class creation dynamic=False which means sub-object
+ dependencies are not resolved. At instance creation and
+ whenever a sub-object is set on an object this method will be
+ invoked to determine whether the dependency is available.
+
+ For sub-object dependencies we also return dependencies for
+ every part of the path, e.g. for a dependency specification
+ like "a.b.c" we return dependencies for sub-object "a" and the
+ sub-sub-object "b" in addition to the dependency on the actual
+ parameter "c" on object "b". This is to ensure that if a
+ sub-object is swapped out we are notified and can update the
+ dynamic dependency to the new object. Even if a sub-object
+ dependency can only partially resolved, e.g. if object "a"
+ does not yet have a sub-object "b" we must watch for changes
+ to "b" on sub-object "a" in case such a subobject is put in "b".
+ """
+ if isinstance(spec, Parameter):
+ inst = spec.owner if isinstance(spec.owner, Parameterized) else None
+ cls = spec.owner if inst is None else type(inst)
+ info = PInfo(inst=inst, cls=cls, name=spec.name,
+ pobj=spec, what='value')
+ return [] if intermediate == 'only' else [info], []
+
+ obj, attr, what = _parse_dependency_spec(spec)
+ if obj is None:
+ src = self_.self_or_cls
+ elif not dynamic:
+ return [], [DInfo(spec=spec)]
+ else:
+ if not hasattr(self_.self_or_cls, obj.split('.')[1]):
+ raise AttributeError(
+ f'Dependency {obj[1:]!r} could not be resolved, {self_.self_or_cls} '
+ f'has no parameter or attribute {obj.split(".")[1]!r}. Ensure '
+ 'the object being depended on is declared before calling the '
+ 'Parameterized constructor.'
+ )
+
+ src = _getattrr(self_.self_or_cls, obj[1::], None)
+ if src is None:
+ path = obj[1:].split('.')
+ deps = []
+ # Attempt to partially resolve subobject path to ensure
+ # that if a subobject is later updated making the full
+ # subobject path available we have to be notified and
+ # set up watchers
+ if len(path) >= 1 and intermediate:
+ sub_src = None
+ subpath = path
+ while sub_src is None and subpath:
+ subpath = subpath[:-1]
+ sub_src = _getattrr(self_.self_or_cls, '.'.join(subpath), None)
+ if subpath:
+ subdeps, _ = self_._spec_to_obj(
+ '.'.join(path[:len(subpath)+1]), dynamic, intermediate)
+ deps += subdeps
+ return deps, [] if intermediate == 'only' else [DInfo(spec=spec)]
+
+ cls, inst = (src, None) if isinstance(src, type) else (type(src), src)
+ if attr == 'param':
+ deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate)
+ for p in src.param:
+ param_deps, param_dynamic_deps = src.param._spec_to_obj(p, dynamic, intermediate)
+ deps += param_deps
+ dynamic_deps += param_dynamic_deps
+ return deps, dynamic_deps
+ elif attr in src.param:
+ info = PInfo(inst=inst, cls=cls, name=attr,
+ pobj=src.param[attr], what=what)
+ elif hasattr(src, attr):
+ attr_obj = getattr(src, attr)
+ if isinstance(attr_obj, Parameterized):
+ return [], []
+ elif isinstance(attr_obj, (FunctionType, MethodType)):
+ info = MInfo(inst=inst, cls=cls, name=attr,
+ method=attr_obj)
+ else:
+ raise AttributeError(f"Attribute {attr!r} could not be resolved on {src}.")
+ elif getattr(src, "abstract", None):
+ return [], [] if intermediate == 'only' else [DInfo(spec=spec)]
+ else:
+ raise AttributeError(f"Attribute {attr!r} could not be resolved on {src}.")
+
+ if obj is None or not intermediate:
+ return [info], []
+ deps, dynamic_deps = self_._spec_to_obj(obj[1:], dynamic, intermediate)
+ if intermediate != 'only':
+ deps.append(info)
+ return deps, dynamic_deps
+
+ def _register_watcher(self_, action, watcher, what='value'):
+ if self_.self is not None and not self_.self._param__private.initialized:
+ warnings.warn(
+ '(Un)registering a watcher on a partially initialized Parameterized instance '
+ 'is deprecated and will raise an error in a future version. Ensure '
+ 'you have called super().__init__(**) in the Parameterized instance '
+ 'constructor before trying to set up a watcher.',
+ category=_ParamFutureWarning,
+ stacklevel=4,
+ )
+
+ parameter_names = watcher.parameter_names
+ for parameter_name in parameter_names:
+ if parameter_name not in self_.cls.param:
+ raise ValueError("{} parameter was not found in list of "
+ "parameters of class {}".format(parameter_name, self_.cls.__name__))
+
+ if self_.self is not None and what == "value":
+ watchers = self_.self._param__private.watchers
+ if parameter_name not in watchers:
+ watchers[parameter_name] = {}
+ if what not in watchers[parameter_name]:
+ watchers[parameter_name][what] = []
+ getattr(watchers[parameter_name][what], action)(watcher)
+ else:
+ watchers = self_[parameter_name].watchers
+ if what not in watchers:
+ watchers[what] = []
+ getattr(watchers[what], action)(watcher)
+
+[docs] def watch(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0):
+ """
+ Register the given callback function `fn` to be invoked for
+ events on the indicated parameters.
+
+ `what`: What to watch on each parameter; either the value (by
+ default) or else the indicated slot (e.g. 'constant').
+
+ `onlychanged`: By default, only invokes the function when the
+ watched item changes, but if `onlychanged=False` also invokes
+ it when the `what` item is set to its current value again.
+
+ `queued`: By default, additional watcher events generated
+ inside the callback fn are dispatched immediately, effectively
+ doing depth-first processing of Watcher events. However, in
+ certain scenarios, it is helpful to wait to dispatch such
+ downstream events until all events that triggered this watcher
+ have been processed. In such cases setting `queued=True` on
+ this Watcher will queue up new downstream events generated
+ during `fn` until `fn` completes and all other watchers
+ invoked by that same event have finished executing),
+ effectively doing breadth-first processing of Watcher events.
+
+ `precedence`: Declares a precedence level for the Watcher that
+ determines the priority with which the callback is executed.
+ Lower precedence levels are executed earlier. Negative
+ precedences are reserved for internal Watchers, i.e. those
+ set up by param.depends.
+
+ When the `fn` is called, it will be provided the relevant
+ Event objects as positional arguments, which allows it to
+ determine which of the possible triggering events occurred.
+
+ Returns a Watcher object.
+
+ See help(Watcher) and help(Event) for the contents of those objects.
+ """
+ if precedence < 0:
+ raise ValueError("User-defined watch callbacks must declare "
+ "a positive precedence. Negative precedences "
+ "are reserved for internal Watchers.")
+ return self_._watch(fn, parameter_names, what, onlychanged, queued, precedence)
+
+ def _watch(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=-1):
+ parameter_names = tuple(parameter_names) if isinstance(parameter_names, list) else (parameter_names,)
+ watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, mode='args',
+ onlychanged=onlychanged, parameter_names=parameter_names,
+ what=what, queued=queued, precedence=precedence)
+ self_._register_watcher('append', watcher, what)
+ return watcher
+
+[docs] def unwatch(self_, watcher):
+ """
+ Remove the given Watcher object (from `watch` or `watch_values`) from this object's list.
+ """
+ try:
+ self_._register_watcher('remove', watcher, what=watcher.what)
+ except Exception:
+ self_.warning(f'No such watcher {str(watcher)} to remove.')
+
+[docs] def watch_values(self_, fn, parameter_names, what='value', onlychanged=True, queued=False, precedence=0):
+ """
+ Easier-to-use version of `watch` specific to watching for changes in parameter values.
+
+ Only allows `what` to be 'value', and invokes the callback `fn` using keyword
+ arguments <param_name>=<new_value> rather than with a list of Event objects.
+ """
+ if precedence < 0:
+ raise ValueError("User-defined watch callbacks must declare "
+ "a positive precedence. Negative precedences "
+ "are reserved for internal Watchers.")
+ assert what == 'value'
+ if isinstance(parameter_names, list):
+ parameter_names = tuple(parameter_names)
+ else:
+ parameter_names = (parameter_names,)
+ watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn,
+ mode='kwargs', onlychanged=onlychanged,
+ parameter_names=parameter_names, what=what,
+ queued=queued, precedence=precedence)
+ self_._register_watcher('append', watcher, what)
+ return watcher
+
+ # Instance methods
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `{k:v.default for k,v in p.param.objects().items()}`")
+ def defaults(self_):
+ """
+ Return {parameter_name:parameter.default} for all non-constant
+ Parameters.
+
+ Note that a Parameter for which instantiate==True has its default
+ instantiated.
+
+ .. deprecated:: 1.12.0
+ Use instead `{k:v.default for k,v in p.param.objects().items()}`
+ """
+ self = self_.self
+ d = {}
+ for param_name, param in self.param.objects('existing').items():
+ if param.constant:
+ pass
+ if param.instantiate:
+ self.param._instantiate_param(param, dict_=d, key=param_name)
+ d[param_name] = param.default
+ return d
+
+ # Designed to avoid any processing unless the print
+ # level is high enough, though not all callers of message(),
+ # verbose(), debug(), etc are taking advantage of this.
+ def __db_print(self_,level,msg,*args,**kw):
+ """
+ Calls the logger returned by the get_logger() function,
+ prepending the result of calling dbprint_prefix() (if any).
+
+ See python's logging module for details.
+ """
+ self_or_cls = self_.self_or_cls
+ if get_logger(name=self_or_cls.name).isEnabledFor(level):
+
+ if dbprint_prefix and callable(dbprint_prefix):
+ msg = dbprint_prefix() + ": " + msg # pylint: disable-msg=E1102
+
+ get_logger(name=self_or_cls.name).log(level, msg, *args, **kw)
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="""Use instead `for k,v in p.param.objects().items(): print(f"{p.__class__.name}.{k}={repr(v.default)}")`""")
+ def print_param_values(self_):
+ """Print the values of all this object's Parameters.
+
+ .. deprecated:: 1.12.0
+ Use instead `for k,v in p.param.objects().items(): print(f"{p.__class__.name}.{k}={repr(v.default)}")`
+ """
+ self = self_.self
+ for name, val in self.param.values().items():
+ print(f'{self.name}.{name} = {val}')
+
+[docs] def warning(self_, msg,*args,**kw):
+ """
+ Print msg merged with args as a warning, unless module variable
+ warnings_as_exceptions is True, then raise an Exception
+ containing the arguments.
+
+ See Python's logging module for details of message formatting.
+ """
+ self_.log(WARNING, msg, *args, **kw)
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `.param.log(param.MESSAGE, ...)`")
+ def message(self_,msg,*args,**kw):
+ """
+ Print msg merged with args as a message.
+
+ See Python's logging module for details of message formatting.
+
+ .. deprecated:: 1.12.0
+ Use instead `.param.log(param.MESSAGE, ...)`
+ """
+ self_.__db_print(INFO,msg,*args,**kw)
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `.param.log(param.VERBOSE, ...)`")
+ def verbose(self_,msg,*args,**kw):
+ """
+ Print msg merged with args as a verbose message.
+
+ See Python's logging module for details of message formatting.
+
+ .. deprecated:: 1.12.0
+ Use instead `.param.log(param.VERBOSE, ...)`
+ """
+ self_.__db_print(VERBOSE,msg,*args,**kw)
+
+ # PARAM3_DEPRECATION
+[docs] @_deprecated(extra_msg="Use instead `.param.log(param.DEBUG, ...)`")
+ def debug(self_,msg,*args,**kw):
+ """
+ Print msg merged with args as a debugging statement.
+
+ See Python's logging module for details of message formatting.
+
+ .. deprecated:: 1.12.0
+ Use instead `.param.log(param.DEBUG, ...)`
+ """
+ self_.__db_print(DEBUG,msg,*args,**kw)
+
+[docs] def log(self_, level, msg, *args, **kw):
+ """
+ Print msg merged with args as a message at the indicated logging level.
+
+ Logging levels include those provided by the Python logging module
+ plus VERBOSE, either obtained directly from the logging module like
+ `logging.INFO`, or from parameterized like `param.parameterized.INFO`.
+
+ Supported logging levels include (in order of severity)
+ DEBUG, VERBOSE, INFO, WARNING, ERROR, CRITICAL
+
+ See Python's logging module for details of message formatting.
+ """
+ if level is WARNING:
+ if warnings_as_exceptions:
+ raise Exception("Warning: " + msg % args)
+ else:
+ global warning_count
+ warning_count+=1
+ self_.__db_print(level, msg, *args, **kw)
+
+ # Note that there's no state_push method on the class, so
+ # dynamic parameters set on a class can't have state saved. This
+ # is because, to do this, state_push() would need to be a
+ # @bothmethod, but that complicates inheritance in cases where we
+ # already have a state_push() method.
+ # (isinstance(g,Parameterized) below is used to exclude classes.)
+
+ def _state_push(self_):
+ """
+ Save this instance's state.
+
+ For Parameterized instances, this includes the state of
+ dynamically generated values.
+
+ Subclasses that maintain short-term state should additionally
+ save and restore that state using state_push() and
+ state_pop().
+
+ Generally, this method is used by operations that need to test
+ something without permanently altering the objects' state.
+ """
+ self = self_.self_or_cls
+ if not isinstance(self, Parameterized):
+ raise NotImplementedError('_state_push is not implemented at the class level')
+ for pname, p in self.param.objects('existing').items():
+ g = self.param.get_value_generator(pname)
+ if hasattr(g,'_Dynamic_last'):
+ g._saved_Dynamic_last.append(g._Dynamic_last)
+ g._saved_Dynamic_time.append(g._Dynamic_time)
+ # CB: not storing the time_fn: assuming that doesn't
+ # change.
+ elif hasattr(g,'state_push') and isinstance(g,Parameterized):
+ g.state_push()
+
+ def _state_pop(self_):
+ """
+ Restore the most recently saved state.
+
+ See state_push() for more details.
+ """
+ self = self_.self_or_cls
+ if not isinstance(self, Parameterized):
+ raise NotImplementedError('_state_pop is not implemented at the class level')
+ for pname, p in self.param.objects('existing').items():
+ g = self.param.get_value_generator(pname)
+ if hasattr(g,'_Dynamic_last'):
+ g._Dynamic_last = g._saved_Dynamic_last.pop()
+ g._Dynamic_time = g._saved_Dynamic_time.pop()
+ elif hasattr(g,'state_pop') and isinstance(g,Parameterized):
+ g.state_pop()
+
+[docs] def pprint(self_, imports=None, prefix=" ", unknown_value='<?>',
+ qualify=False, separator=""):
+ """
+ (Experimental) Pretty printed representation that may be
+ evaluated with eval. See pprint() function for more details.
+ """
+ self = self_.self_or_cls
+ if not isinstance(self, Parameterized):
+ raise NotImplementedError('pprint is not implemented at the class level')
+ # Wrapping the staticmethod _pprint with partial to pass `self` as the `_recursive_repr`
+ # decorator expects `self`` to be the pprinted object (not `self_`).
+ return partial(self_._pprint, self, imports=imports, prefix=prefix,
+ unknown_value=unknown_value, qualify=qualify, separator=separator)()
+
+ @staticmethod
+ @_recursive_repr()
+ def _pprint(self, imports=None, prefix=" ", unknown_value='<?>',
+ qualify=False, separator=""):
+ if imports is None:
+ imports = [] # would have been simpler to use a set from the start
+ imports[:] = list(set(imports))
+
+ # Generate import statement
+ mod = self.__module__
+ bits = mod.split('.')
+ imports.append("import %s"%mod)
+ imports.append("import %s"%bits[0])
+
+ changed_params = self.param.values(onlychanged=script_repr_suppress_defaults)
+ values = self.param.values()
+ spec = getfullargspec(type(self).__init__)
+ if 'self' not in spec.args or spec.args[0] != 'self':
+ raise KeyError(f"'{type(self).__name__}.__init__.__signature__' must contain 'self' as its first Parameter.")
+ args = spec.args[1:]
+
+ if spec.defaults is not None:
+ posargs = spec.args[:-len(spec.defaults)]
+ kwargs = dict(zip(spec.args[-len(spec.defaults):], spec.defaults))
+ else:
+ posargs, kwargs = args, []
+
+ parameters = self.param.objects('existing')
+ ordering = sorted(
+ sorted(changed_params), # alphanumeric tie-breaker
+ key=lambda k: (- float('inf') # No precedence is lowest possible precendence
+ if parameters[k].precedence is None else
+ parameters[k].precedence))
+
+ arglist, keywords, processed = [], [], []
+ for k in args + ordering:
+ if k in processed: continue
+
+ # Suppresses automatically generated names.
+ if k == 'name' and (values[k] is not None
+ and re.match('^'+self.__class__.__name__+'[0-9]+$', values[k])):
+ continue
+
+ value = pprint(values[k], imports, prefix=prefix,settings=[],
+ unknown_value=unknown_value,
+ qualify=qualify) if k in values else None
+
+ if value is None:
+ if unknown_value is False:
+ raise Exception(f"{self.name}: unknown value of {k!r}")
+ elif unknown_value is None:
+ # i.e. suppress repr
+ continue
+ else:
+ value = unknown_value
+
+ # Explicit kwarg (unchanged, known value)
+ if (k in kwargs) and (k in values) and kwargs[k] == values[k]: continue
+
+ if k in posargs:
+ # value will be unknown_value unless k is a parameter
+ arglist.append(value)
+ elif (k in kwargs or
+ (hasattr(spec, 'varkw') and (spec.varkw is not None)) or
+ (hasattr(spec, 'keywords') and (spec.keywords is not None))):
+ # Explicit modified keywords or parameters in
+ # precendence order (if **kwargs present)
+ keywords.append(f'{k}={value}')
+
+ processed.append(k)
+
+ qualifier = mod + '.' if qualify else ''
+ arguments = arglist + keywords + (['**%s' % spec.varargs] if spec.varargs else [])
+ return qualifier + '{}({})'.format(self.__class__.__name__, (','+separator+prefix).join(arguments))
+
+
+class ParameterizedMetaclass(type):
+ """
+ The metaclass of Parameterized (and all its descendents).
+
+ The metaclass overrides type.__setattr__ to allow us to set
+ Parameter values on classes without overwriting the attribute
+ descriptor. That is, for a Parameterized class of type X with a
+ Parameter y, the user can type X.y=3, which sets the default value
+ of Parameter y to be 3, rather than overwriting y with the
+ constant value 3 (and thereby losing all other info about that
+ Parameter, such as the doc string, bounds, etc.).
+
+ The __init__ method is used when defining a Parameterized class,
+ usually when the module where that class is located is imported
+ for the first time. That is, the __init__ in this metaclass
+ initializes the *class* object, while the __init__ method defined
+ in each Parameterized class is called for each new instance of
+ that class.
+
+ Additionally, a class can declare itself abstract by having an
+ attribute __abstract set to True. The 'abstract' attribute can be
+ used to find out if a class is abstract or not.
+ """
+ def __init__(mcs, name, bases, dict_):
+ """
+ Initialize the class object (not an instance of the class, but
+ the class itself).
+
+ Initializes all the Parameters by looking up appropriate
+ default values (see __param_inheritance()) and setting
+ attrib_names (see _set_names()).
+ """
+ type.__init__(mcs, name, bases, dict_)
+
+ # Compute which parameters explicitly do not support references
+ # This can be removed when Parameter.allow_refs=True by default.
+ explicit_no_refs = set()
+ for base in bases:
+ if issubclass(base, Parameterized):
+ explicit_no_refs |= set(base._param__private.explicit_no_refs)
+
+ _param__private = _ClassPrivate(explicit_no_refs=list(explicit_no_refs))
+ mcs._param__private = _param__private
+ mcs.__set_name(name, dict_)
+ mcs._param__parameters = Parameters(mcs)
+
+ # All objects (with their names) of type Parameter that are
+ # defined in this class
+ parameters = [(n, o) for (n, o) in dict_.items()
+ if isinstance(o, Parameter)]
+
+ for param_name,param in parameters:
+ mcs._initialize_parameter(param_name, param)
+
+ # retrieve depends info from methods and store more conveniently
+ dependers = [(n, m, m._dinfo) for (n, m) in dict_.items()
+ if hasattr(m, '_dinfo')]
+
+ # Resolve dependencies of current class
+ _watch = []
+ for name, method, dinfo in dependers:
+ watch = dinfo.get('watch', False)
+ on_init = dinfo.get('on_init', False)
+ minfo = MInfo(cls=mcs, inst=None, name=name,
+ method=method)
+ deps, dynamic_deps = _params_depended_on(minfo, dynamic=False)
+ if watch:
+ _watch.append((name, watch == 'queued', on_init, deps, dynamic_deps))
+
+ # Resolve dependencies in class hierarchy
+ _inherited = []
+ for cls in classlist(mcs)[:-1][::-1]:
+ if not hasattr(cls, '_param__parameters'):
+ continue
+ for dep in cls.param._depends['watch']:
+ method = getattr(mcs, dep[0], None)
+ dinfo = getattr(method, '_dinfo', {'watch': False})
+ if (not any(dep[0] == w[0] for w in _watch+_inherited)
+ and dinfo.get('watch')):
+ _inherited.append(dep)
+
+ mcs.param._depends = {'watch': _inherited+_watch}
+
+ if docstring_signature:
+ mcs.__class_docstring()
+
+ def __set_name(mcs, name, dict_):
+ """
+ Give Parameterized classes a useful 'name' attribute that is by
+ default the class name, unless a class in the hierarchy has defined
+ a `name` String Parameter with a defined `default` value, in which case
+ that value is used to set the class name.
+ """
+ name_param = dict_.get("name", None)
+ if name_param is not None:
+ if not type(name_param) is String:
+ raise TypeError(
+ f"Parameterized class {name!r} cannot override "
+ f"the 'name' Parameter with type {type(name_param)}. "
+ "Overriding 'name' is only allowed with a 'String' Parameter."
+ )
+ if name_param.default:
+ mcs.name = name_param.default
+ mcs._param__private.renamed = True
+ else:
+ mcs.name = name
+ else:
+ classes = classlist(mcs)[::-1]
+ found_renamed = False
+ for c in classes:
+ if hasattr(c, '_param__private') and c._param__private.renamed:
+ found_renamed = True
+ break
+ if not found_renamed:
+ mcs.name = name
+
+ def __class_docstring(mcs):
+ """
+ Customize the class docstring with a Parameter table if
+ `docstring_describe_params` and the `param_pager` is available.
+ """
+ if not docstring_describe_params or not param_pager:
+ return
+ class_docstr = mcs.__doc__ if mcs.__doc__ else ''
+ description = param_pager(mcs)
+ mcs.__doc__ = class_docstr + '\n' + description
+
+ def _initialize_parameter(mcs, param_name, param):
+ # A Parameter has no way to find out the name a
+ # Parameterized class has for it
+ param._set_names(param_name)
+ mcs.__param_inheritance(param_name, param)
+
+ # Should use the official Python 2.6+ abstract base classes; see
+ # https://github.com/holoviz/param/issues/84
+ def __is_abstract(mcs):
+ """
+ Return True if the class has an attribute __abstract set to True.
+ Subclasses will return False unless they themselves have
+ __abstract set to true. This mechanism allows a class to
+ declare itself to be abstract (e.g. to avoid it being offered
+ as an option in a GUI), without the "abstract" property being
+ inherited by its subclasses (at least one of which is
+ presumably not abstract).
+ """
+ # Can't just do ".__abstract", because that is mangled to
+ # _ParameterizedMetaclass__abstract before running, but
+ # the actual class object will have an attribute
+ # _ClassName__abstract. So, we have to mangle it ourselves at
+ # runtime. Mangling follows description in
+ # https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
+ try:
+ return getattr(mcs,'_%s__abstract'%mcs.__name__.lstrip("_"))
+ except AttributeError:
+ return False
+
+ def __get_signature(mcs):
+ """
+ For classes with a constructor signature that matches the default
+ Parameterized.__init__ signature (i.e. ``__init__(self, **params)``)
+ this method will generate a new signature that expands the
+ parameters. If the signature differs from the default the
+ custom signature is returned.
+ """
+ if mcs._param__private.signature:
+ return mcs._param__private.signature
+ # allowed_signature must be the signature of Parameterized.__init__
+ # Inspecting `mcs.__init__` instead of `mcs` to avoid a recursion error
+ if inspect.signature(mcs.__init__) != DEFAULT_SIGNATURE:
+ return None
+ processed_kws, keyword_groups = set(), []
+ for cls in reversed(mcs.mro()):
+ keyword_group = []
+ for k, v in sorted(cls.__dict__.items()):
+ if isinstance(v, Parameter) and k not in processed_kws and not v.readonly:
+ keyword_group.append(k)
+ processed_kws.add(k)
+ keyword_groups.append(keyword_group)
+
+ keywords = [el for grp in reversed(keyword_groups) for el in grp]
+ mcs._param__private.signature = signature = inspect.Signature([
+ inspect.Parameter(k, inspect.Parameter.KEYWORD_ONLY)
+ for k in keywords
+ ])
+ return signature
+
+ __signature__ = property(__get_signature)
+
+ abstract = property(__is_abstract)
+
+ def _get_param(mcs):
+ return mcs._param__parameters
+
+ param = property(_get_param)
+
+ def __setattr__(mcs, attribute_name, value):
+ """
+ Implements 'self.attribute_name=value' in a way that also supports Parameters.
+
+ If there is already a descriptor named attribute_name, and
+ that descriptor is a Parameter, and the new value is *not* a
+ Parameter, then call that Parameter's __set__ method with the
+ specified value.
+
+ In all other cases set the attribute normally (i.e. overwrite
+ the descriptor). If the new value is a Parameter, once it has
+ been set we make sure that the value is inherited from
+ Parameterized superclasses as described in __param_inheritance().
+ """
+ # Find out if there's a Parameter called attribute_name as a
+ # class attribute of this class - if not, parameter is None.
+ parameter,owning_class = mcs.get_param_descriptor(attribute_name)
+
+ if parameter and not isinstance(value,Parameter):
+ if owning_class != mcs:
+ parameter = copy.copy(parameter)
+ parameter.owner = mcs
+ type.__setattr__(mcs,attribute_name,parameter)
+ mcs.__dict__[attribute_name].__set__(None,value)
+
+ else:
+ type.__setattr__(mcs,attribute_name,value)
+
+ if isinstance(value,Parameter):
+ mcs.__param_inheritance(attribute_name,value)
+
+ def __param_inheritance(mcs, param_name, param):
+ """
+ Look for Parameter values in superclasses of this
+ Parameterized class.
+
+ Ordinarily, when a Python object is instantiated, attributes
+ not given values in the constructor will inherit the value
+ given in the object's class, or in its superclasses. For
+ Parameters owned by Parameterized classes, we have implemented
+ an additional level of default lookup, should this ordinary
+ lookup return only `Undefined`.
+
+ In such a case, i.e. when no non-`Undefined` value was found for a
+ Parameter by the usual inheritance mechanisms, we explicitly
+ look for Parameters with the same name in superclasses of this
+ Parameterized class, and use the first such value that we
+ find.
+
+ The goal is to be able to set the default value (or other
+ slots) of a Parameter within a Parameterized class, just as we
+ can set values for non-Parameter objects in Parameterized
+ classes, and have the values inherited through the
+ Parameterized hierarchy as usual.
+
+ Note that instantiate is handled differently: if there is a
+ parameter with the same name in one of the superclasses with
+ instantiate set to True, this parameter will inherit
+ instantiate=True.
+ """
+ # get all relevant slots (i.e. slots defined in all
+ # superclasses of this parameter)
+ p_type = type(param)
+ slots = dict.fromkeys(p_type._all_slots_)
+
+ # note for some eventual future: python 3.6+ descriptors grew
+ # __set_name__, which could replace this and _set_names
+ setattr(param, 'owner', mcs)
+ del slots['owner']
+
+ # backwards compatibility (see Composite parameter)
+ if 'objtype' in slots:
+ setattr(param, 'objtype', mcs)
+ del slots['objtype']
+
+ supers = classlist(mcs)[::-1]
+
+ # Explicitly inherit instantiate from super class and
+ # check if type has changed to a more specific or different
+ # Parameter type, requiring extra validation
+ type_change = False
+ for superclass in supers:
+ super_param = superclass.__dict__.get(param_name)
+ if not isinstance(super_param, Parameter):
+ continue
+ if super_param.instantiate is True:
+ param.instantiate = True
+ super_type = type(super_param)
+ if not issubclass(super_type, p_type):
+ type_change = True
+ del slots['instantiate']
+
+ callables, slot_values = {}, {}
+ slot_overridden = False
+ for slot in slots.keys():
+ # Search up the hierarchy until param.slot (which has to
+ # be obtained using getattr(param,slot)) is not Undefined,
+ # is a new value (using identity) or we run out of classes
+ # to search.
+ for scls in supers:
+ # Class may not define parameter or slot might not be
+ # there because could be a more general type of Parameter
+ new_param = scls.__dict__.get(param_name)
+ if new_param is None or not hasattr(new_param, slot):
+ continue
+
+ new_value = getattr(new_param, slot)
+ old_value = slot_values.get(slot, Undefined)
+ if new_value is Undefined:
+ continue
+ elif new_value is old_value:
+ continue
+ elif old_value is Undefined:
+ slot_values[slot] = new_value
+ # If we already know we have to re-validate abort
+ # early to avoid costly lookups
+ if slot_overridden or type_change:
+ break
+ else:
+ if slot not in param._non_validated_slots:
+ slot_overridden = True
+ break
+
+ if slot_values.get(slot, Undefined) is Undefined:
+ try:
+ default_val = param._slot_defaults[slot]
+ except KeyError as e:
+ raise KeyError(
+ f'Slot {slot!r} of parameter {param_name!r} has no '
+ 'default value defined in `_slot_defaults`'
+ ) from e
+ if callable(default_val):
+ callables[slot] = default_val
+ else:
+ slot_values[slot] = default_val
+ elif slot == 'allow_refs' and not slot_values[slot]:
+ # Track Parameters that explicitly declared no refs
+ mcs._param__private.explicit_no_refs.append(param.name)
+
+ # Now set the actual slot values
+ for slot, value in slot_values.items():
+ setattr(param, slot, value)
+
+ # Avoid crosstalk between mutable slot values in different Parameter objects
+ if slot != "default":
+ v = getattr(param, slot)
+ if _is_mutable_container(v):
+ setattr(param, slot, copy.copy(v))
+
+ # Once all the static slots have been filled in, fill in the dynamic ones
+ # (which are only allowed to use static values or results are undefined)
+ for slot, fn in callables.items():
+ setattr(param, slot, fn(param))
+
+ # Once all the slot values have been set, call _update_state for Parameters
+ # that need updates to make sure they're set up correctly after inheritance.
+ param._update_state()
+
+ # If the type has changed to a more specific or different type
+ # or a slot value has been changed validate the default again.
+
+ # Hack: Had to disable re-validation of None values because the
+ # automatic appending of an unknown value on Selector opens a whole
+ # rabbit hole in regard to the validation.
+ if type_change or slot_overridden and param.default is not None:
+ param._validate(param.default)
+
+ def get_param_descriptor(mcs,param_name):
+ """
+ Goes up the class hierarchy (starting from the current class)
+ looking for a Parameter class attribute param_name. As soon as
+ one is found as a class attribute, that Parameter is returned
+ along with the class in which it is declared.
+ """
+ classes = classlist(mcs)
+ for c in classes[::-1]:
+ attribute = c.__dict__.get(param_name)
+ if isinstance(attribute,Parameter):
+ return attribute,c
+ return None,None
+
+
+
+# Whether script_repr should avoid reporting the values of parameters
+# that are just inheriting their values from the class defaults.
+# Because deepcopying creates a new object, cannot detect such
+# inheritance when instantiate = True, so such values will be printed
+# even if they are just being copied from the default.
+script_repr_suppress_defaults=True
+
+
+[docs]def script_repr(val, imports=None, prefix="\n ", settings=[],
+ qualify=True, unknown_value=None, separator="\n",
+ show_imports=True):
+ """
+ Variant of pprint() designed for generating a (nearly) runnable script.
+
+ The output of script_repr(parameterized_obj) is meant to be a
+ string suitable for running using `python file.py`. Not every
+ object is guaranteed to have a runnable script_repr
+ representation, but it is meant to be a good starting point for
+ generating a Python script that (after minor edits) can be
+ evaluated to get a newly initialized object similar to the one
+ provided.
+
+ The new object will only have the same parameter state, not the
+ same internal (attribute) state; the script_repr captures only
+ the state of the Parameters of that object and not any other
+ attributes it may have.
+
+ If show_imports is True (default), includes import statements
+ for each of the modules required for the objects being
+ instantiated. This list may not be complete, as it typically
+ includes only the imports needed for the Parameterized object
+ itself, not for values that may have been supplied to Parameters.
+
+ Apart from show_imports, accepts the same arguments as pprint(),
+ so see pprint() for explanations of the arguments accepted. The
+ default values of each of these arguments differ from pprint() in
+ ways that are more suitable for saving as a separate script than
+ for e.g. pretty-printing at the Python prompt.
+ """
+
+ if imports is None:
+ imports = []
+
+ rep = pprint(val, imports, prefix, settings, unknown_value,
+ qualify, separator)
+
+ imports = list(set(imports))
+ imports_str = ("\n".join(imports) + "\n\n") if show_imports else ""
+
+ return imports_str + rep
+
+
+# PARAM2_DEPRECATION: Remove entirely unused settings argument
+def pprint(val,imports=None, prefix="\n ", settings=[],
+ unknown_value='<?>', qualify=False, separator=''):
+ """
+ Pretty printed representation of a parameterized
+ object that may be evaluated with eval.
+
+ Similar to repr except introspection of the constructor (__init__)
+ ensures a valid and succinct representation is generated.
+
+ Only parameters are represented (whether specified as standard,
+ positional, or keyword arguments). Parameters specified as
+ positional arguments are always shown, followed by modified
+ parameters specified as keyword arguments, sorted by precedence.
+
+ unknown_value determines what to do where a representation cannot be
+ generated for something required to recreate the object. Such things
+ include non-parameter positional and keyword arguments, and certain
+ values of parameters (e.g. some random state objects).
+
+ Supplying an unknown_value of None causes unrepresentable things
+ to be silently ignored. If unknown_value is a string, that
+ string will appear in place of any unrepresentable things. If
+ unknown_value is False, an Exception will be raised if an
+ unrepresentable value is encountered.
+
+ If supplied, imports should be a list, and it will be populated
+ with the set of imports required for the object and all of its
+ parameter values.
+
+ If qualify is True, the class's path will be included (e.g. "a.b.C()"),
+ otherwise only the class will appear ("C()").
+
+ Parameters will be separated by a comma only by default, but the
+ separator parameter allows an additional separator to be supplied
+ (e.g. a newline could be supplied to have each Parameter appear on a
+ separate line).
+
+ Instances of types that require special handling can use the
+ script_repr_reg dictionary. Using the type as a key, add a
+ function that returns a suitable representation of instances of
+ that type, and adds the required import statement. The repr of a
+ parameter can be suppressed by returning None from the appropriate
+ hook in script_repr_reg.
+ """
+
+ if imports is None:
+ imports = []
+
+ if isinstance(val,type):
+ rep = type_script_repr(val,imports,prefix,settings)
+
+ elif type(val) in script_repr_reg:
+ rep = script_repr_reg[type(val)](val,imports,prefix,settings)
+
+ elif isinstance(val, Parameterized) or (type(val) is type and issubclass(val, Parameterized)):
+ rep=val.param.pprint(imports=imports, prefix=prefix+" ",
+ qualify=qualify, unknown_value=unknown_value,
+ separator=separator)
+ else:
+ rep=repr(val)
+
+ return rep
+
+
+# Registry for special handling for certain types in script_repr and pprint
+script_repr_reg = {}
+
+
+# currently only handles list and tuple
+def container_script_repr(container,imports,prefix,settings):
+ result=[]
+ for i in container:
+ result.append(pprint(i,imports,prefix,settings))
+
+ ## (hack to get container brackets)
+ if isinstance(container,list):
+ d1,d2='[',']'
+ elif isinstance(container,tuple):
+ d1,d2='(',')'
+ else:
+ raise NotImplementedError
+ rep=d1+','.join(result)+d2
+
+ # no imports to add for built-in types
+
+ return rep
+
+
+def empty_script_repr(*args): # pyflakes:ignore (unused arguments):
+ return None
+
+try:
+ # Suppress scriptrepr for objects not yet having a useful string representation
+ import numpy
+ script_repr_reg[random.Random] = empty_script_repr
+ script_repr_reg[numpy.random.RandomState] = empty_script_repr
+
+except ImportError:
+ pass # Support added only if those libraries are available
+
+
+def function_script_repr(fn,imports,prefix,settings):
+ name = fn.__name__
+ module = fn.__module__
+ imports.append('import %s'%module)
+ return module+'.'+name
+
+def type_script_repr(type_,imports,prefix,settings):
+ module = type_.__module__
+ if module!='__builtin__':
+ imports.append('import %s'%module)
+ return module+'.'+type_.__name__
+
+script_repr_reg[list] = container_script_repr
+script_repr_reg[tuple] = container_script_repr
+script_repr_reg[FunctionType] = function_script_repr
+
+
+#: If not None, the value of this Parameter will be called (using '()')
+#: before every call to __db_print, and is expected to evaluate to a
+#: string that is suitable for prefixing messages and warnings (such
+#: as some indicator of the global state).
+dbprint_prefix=None
+
+
+def truncate(str_, maxlen = 30):
+ """Return HTML-safe truncated version of given string"""
+ rep = (str_[:(maxlen-2)] + '..') if (len(str_) > (maxlen-2)) else str_
+ return html.escape(rep)
+
+
+def _get_param_repr(key, val, p, vallen=30, doclen=40):
+ """HTML representation for a single Parameter object and its value"""
+ if isinstance(val, Parameterized) or (type(val) is type and issubclass(val, Parameterized)):
+ value = val.param._repr_html_(open=False)
+ elif hasattr(val, "_repr_html_"):
+ value = val._repr_html_()
+ else:
+ value = truncate(repr(val), vallen)
+
+ if hasattr(p, 'bounds'):
+ if p.bounds is None:
+ range_ = ''
+ elif hasattr(p,'inclusive_bounds'):
+ # Numeric bounds use ( and [ to indicate exclusive and inclusive
+ bl,bu = p.bounds
+ il,iu = p.inclusive_bounds
+
+ lb = '' if bl is None else ('>=' if il else '>') + str(bl)
+ ub = '' if bu is None else ('<=' if iu else '<') + str(bu)
+ range_ = lb + (', ' if lb and bu else '') + ub
+ else:
+ range_ = repr(p.bounds)
+ elif hasattr(p, 'objects') and p.objects:
+ range_ = ', '.join(list(map(repr, p.objects)))
+ elif hasattr(p, 'class_'):
+ if isinstance(p.class_, tuple):
+ range_ = ' | '.join(kls.__name__ for kls in p.class_)
+ else:
+ range_ = p.class_.__name__
+ elif hasattr(p, 'regex') and p.regex is not None:
+ range_ = f'regex({p.regex})'
+ else:
+ range_ = ''
+
+ if p.readonly:
+ range_ = ' '.join(s for s in ['<i>read-only</i>', range_] if s)
+ elif p.constant:
+ range_ = ' '.join(s for s in ['<i>constant</i>', range_] if s)
+
+ if getattr(p, 'allow_None', False):
+ range_ = ' '.join(s for s in ['<i>nullable</i>', range_] if s)
+
+ tooltip = f' class="param-doc-tooltip" data-tooltip="{escape(p.doc.strip())}"' if p.doc else ''
+
+ return (
+ f'<tr>'
+ f' <td><p style="margin-bottom: 0px;"{tooltip}>{key}</p></td>'
+ f' <td style="max-width: 200px; text-align:left;">{value}</td>'
+ f' <td style="text-align:left;">{p.__class__.__name__}</td>'
+ f' <td style="max-width: 300px;">{range_}</td>'
+ f'</tr>\n'
+ )
+
+
+def _parameterized_repr_html(p, open):
+ """HTML representation for a Parameterized object"""
+ if isinstance(p, Parameterized):
+ cls = p.__class__
+ title = cls.name + "()"
+ value_field = 'Value'
+ else:
+ cls = p
+ title = cls.name
+ value_field = 'Default'
+
+ tooltip_css = """
+.param-doc-tooltip{
+ position: relative;
+ cursor: help;
+}
+.param-doc-tooltip:hover:after{
+ content: attr(data-tooltip);
+ background-color: black;
+ color: #fff;
+ border-radius: 3px;
+ padding: 10px;
+ position: absolute;
+ z-index: 1;
+ top: -5px;
+ left: 100%;
+ margin-left: 10px;
+ min-width: 250px;
+}
+.param-doc-tooltip:hover:before {
+ content: "";
+ position: absolute;
+ top: 50%;
+ left: 100%;
+ margin-top: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent black transparent transparent;
+}
+"""
+ openstr = " open" if open else ""
+ param_values = p.param.values().items()
+ contents = "".join(_get_param_repr(key, val, p.param[key])
+ for key, val in param_values)
+ return (
+ f'<style>{tooltip_css}</style>\n'
+ f'<details {openstr}>\n'
+ ' <summary style="display:list-item; outline:none;">\n'
+ f' <tt>{title}</tt>\n'
+ ' </summary>\n'
+ ' <div style="padding-left:10px; padding-bottom:5px;">\n'
+ ' <table style="max-width:100%; border:1px solid #AAAAAA;">\n'
+ f' <tr><th style="text-align:left;">Name</th><th style="text-align:left;">{value_field}</th><th style="text-align:left;">Type</th><th>Range</th></tr>\n'
+ f'{contents}\n'
+ ' </table>\n </div>\n</details>\n'
+ )
+
+# _ClassPrivate and _InstancePrivate are the private namespaces of Parameterized
+# classes and instance respectively, stored on the `_param__private` attribute.
+# They are implemented with slots for performance reasons.
+
+class _ClassPrivate:
+ """
+ parameters_state: dict
+ Dict holding some transient states
+ disable_instance_params: bool
+ Whether to disable instance parameters
+ renamed: bool
+ Whethe the class has been renamed by a super class
+ params: dict
+ Dict of parameter_name:parameter
+ """
+
+ __slots__ = [
+ 'parameters_state',
+ 'disable_instance_params',
+ 'renamed',
+ 'params',
+ 'initialized',
+ 'signature',
+ 'explicit_no_refs',
+ ]
+
+ def __init__(
+ self,
+ parameters_state=None,
+ disable_instance_params=False,
+ explicit_no_refs=None,
+ renamed=False,
+ params=None,
+ ):
+ if parameters_state is None:
+ parameters_state = {
+ "BATCH_WATCH": False, # If true, Event and watcher objects are queued.
+ "TRIGGER": False,
+ "events": [], # Queue of batched events
+ "watchers": [] # Queue of batched watchers
+ }
+ self.parameters_state = parameters_state
+ self.disable_instance_params = disable_instance_params
+ self.renamed = renamed
+ self.params = {} if params is None else params
+ self.initialized = False
+ self.signature = None
+ self.explicit_no_refs = [] if explicit_no_refs is None else explicit_no_refs
+
+ def __getstate__(self):
+ return {slot: getattr(self, slot) for slot in self.__slots__}
+
+ def __setstate__(self, state):
+ for k, v in state.items():
+ setattr(self, k, v)
+
+
+class _InstancePrivate:
+ """
+ initialized: bool
+ Flag that can be tested to see if e.g. constant Parameters can still be set
+ parameters_state: dict
+ Dict holding some transient states
+ dynamic_watchers: defaultdict
+ Dynamic watchers
+ ref_watchers: list[Watcher]
+ Watchers used for internal references
+ params: dict
+ Dict of parameter_name:parameter
+ refs: dict
+ Dict of parameter name:reference
+ watchers: dict
+ Dict of dict:
+ parameter_name:
+ parameter_attribute (e.g. 'value'): list of `Watcher`s
+ values: dict
+ Dict of parameter name: value
+ """
+
+ __slots__ = [
+ 'initialized',
+ 'parameters_state',
+ 'dynamic_watchers',
+ 'params',
+ 'async_refs',
+ 'refs',
+ 'ref_watchers',
+ 'syncing',
+ 'watchers',
+ 'values',
+ ]
+
+ def __init__(
+ self,
+ initialized=False,
+ parameters_state=None,
+ dynamic_watchers=None,
+ refs=None,
+ params=None,
+ watchers=None,
+ values=None,
+ ):
+ self.initialized = initialized
+ self.syncing = set()
+ if parameters_state is None:
+ parameters_state = {
+ "BATCH_WATCH": False, # If true, Event and watcher objects are queued.
+ "TRIGGER": False,
+ "events": [], # Queue of batched events
+ "watchers": [] # Queue of batched watchers
+ }
+ self.ref_watchers = []
+ self.async_refs = {}
+ self.parameters_state = parameters_state
+ self.dynamic_watchers = defaultdict(list) if dynamic_watchers is None else dynamic_watchers
+ self.params = {} if params is None else params
+ self.refs = {} if refs is None else refs
+ self.watchers = {} if watchers is None else watchers
+ self.values = {} if values is None else values
+
+ def __getstate__(self):
+ return {slot: getattr(self, slot) for slot in self.__slots__}
+
+ def __setstate__(self, state):
+ for k, v in state.items():
+ setattr(self, k, v)
+
+
+[docs]class Parameterized(metaclass=ParameterizedMetaclass):
+ """
+ Base class for named objects that support Parameters and message
+ formatting.
+
+ Automatic object naming: Every Parameterized instance has a name
+ parameter. If the user doesn't designate a name=<str> argument
+ when constructing the object, the object will be given a name
+ consisting of its class name followed by a unique 5-digit number.
+
+ Automatic parameter setting: The Parameterized __init__ method
+ will automatically read the list of keyword parameters. If any
+ keyword matches the name of a Parameter (see Parameter class)
+ defined in the object's class or any of its superclasses, that
+ parameter in the instance will get the value given as a keyword
+ argument. For example:
+
+ class Foo(Parameterized):
+ xx = Parameter(default=1)
+
+ foo = Foo(xx=20)
+
+ in this case foo.xx gets the value 20.
+
+ When initializing a Parameterized instance ('foo' in the example
+ above), the values of parameters can be supplied as keyword
+ arguments to the constructor (using parametername=parametervalue);
+ these values will override the class default values for this one
+ instance.
+
+ If no 'name' parameter is supplied, self.name defaults to the
+ object's class name with a unique number appended to it.
+
+ Message formatting: Each Parameterized instance has several
+ methods for optionally printing output. This functionality is
+ based on the standard Python 'logging' module; using the methods
+ provided here, wraps calls to the 'logging' module's root logger
+ and prepends each message with information about the instance
+ from which the call was made. For more information on how to set
+ the global logging level and change the default message prefix,
+ see documentation for the 'logging' module.
+ """
+
+ name = String(default=None, constant=True, doc="""
+ String identifier for this object.""")
+
+[docs] def __init__(self, **params):
+ global object_count
+
+ # Setting a Parameter value in an __init__ block before calling
+ # Parameterized.__init__ (via super() generally) already sets the
+ # _InstancePrivate namespace over the _ClassPrivate namespace
+ # (see Parameter.__set__) so we shouldn't override it here.
+ if not isinstance(self._param__private, _InstancePrivate):
+ self._param__private = _InstancePrivate()
+
+ # Skip generating a custom instance name when a class in the hierarchy
+ # has overriden the default of the `name` Parameter.
+ if self.param.name.default == self.__class__.__name__:
+ self.param._generate_name()
+ refs, deps = self.param._setup_params(**params)
+ object_count += 1
+
+ self._param__private.initialized = True
+
+ self.param._setup_refs(deps)
+ self.param._update_deps(init=True)
+ self._param__private.refs = refs
+
+ @property
+ def param(self):
+ return Parameters(self.__class__, self=self)
+
+ #PARAM3_DEPRECATION
+ @property
+ @_deprecated(extra_msg="Use `inst.param.watchers` instead.", warning_cat=FutureWarning)
+ def _param_watchers(self):
+ return self._param__private.watchers
+
+ #PARAM3_DEPRECATION
+ @_param_watchers.setter
+ @_deprecated(extra_msg="Use `inst.param.watchers = ...` instead.", warning_cat=FutureWarning)
+ def _param_watchers(self, value):
+ self._param__private.watchers = value
+
+ # 'Special' methods
+
+ def __getstate__(self):
+ """
+ Save the object's state: return a dictionary that is a shallow
+ copy of the object's __dict__ and that also includes the
+ object's __slots__ (if it has any).
+ """
+ # Unclear why this is a copy and not simply state.update(self.__dict__)
+ state = self.__dict__.copy()
+ for slot in get_occupied_slots(self):
+ state[slot] = getattr(self,slot)
+
+ # Note that Parameterized object pickling assumes that
+ # attributes to be saved are only in __dict__ or __slots__
+ # (the standard Python places to store attributes, so that's a
+ # reasonable assumption). (Additionally, class attributes that
+ # are Parameters are also handled, even when they haven't been
+ # instantiated - see PickleableClassAttributes.)
+
+ return state
+
+ def __setstate__(self, state):
+ """
+ Restore objects from the state dictionary to this object.
+
+ During this process the object is considered uninitialized.
+ """
+ self._param__private = _InstancePrivate()
+ self._param__private.initialized = False
+
+ _param__private = state.get('_param__private', None)
+ if _param__private is None:
+ _param__private = _InstancePrivate()
+
+ # When making a copy the internal watchers have to be
+ # recreated and point to the new instance
+ if _param__private.watchers:
+ param_watchers = _param__private.watchers
+ for p, attrs in param_watchers.items():
+ for attr, watchers in attrs.items():
+ new_watchers = []
+ for watcher in watchers:
+ watcher_args = list(watcher)
+ if watcher.inst is not None:
+ watcher_args[0] = self
+ fn = watcher.fn
+ if hasattr(fn, '_watcher_name'):
+ watcher_args[2] = _m_caller(self, fn._watcher_name)
+ elif get_method_owner(fn) is watcher.inst:
+ watcher_args[2] = getattr(self, fn.__name__)
+ new_watchers.append(Watcher(*watcher_args))
+ param_watchers[p][attr] = new_watchers
+
+ state.pop('param', None)
+
+ for name,value in state.items():
+ setattr(self,name,value)
+ self._param__private.initialized = True
+
+ @_recursive_repr()
+ def __repr__(self):
+ """
+ Provide a nearly valid Python representation that could be used to recreate
+ the item with its parameters, if executed in the appropriate environment.
+
+ Returns 'classname(parameter1=x,parameter2=y,...)', listing
+ all the parameters of this object.
+ """
+ try:
+ settings = [f'{name}={val!r}'
+ for name, val in self.param.values().items()]
+ except RuntimeError: # Handle recursion in parameter depth
+ settings = []
+ return self.__class__.__name__ + "(" + ", ".join(settings) + ")"
+
+ def __str__(self):
+ """Return a short representation of the name and class of this object."""
+ return f"<{self.__class__.__name__} {self.name}>"
+
+
+def print_all_param_defaults():
+ """Print the default values for all imported Parameters."""
+ print("_______________________________________________________________________________")
+ print("")
+ print(" Parameter Default Values")
+ print("")
+ classes = descendents(Parameterized)
+ classes.sort(key=lambda x:x.__name__)
+ for c in classes:
+ c.print_param_defaults()
+ print("_______________________________________________________________________________")
+
+
+
+# As of Python 2.6+, a fn's **args no longer has to be a
+# dictionary. This might allow us to use a decorator to simplify using
+# ParamOverrides (if that does indeed make them simpler to use).
+# http://docs.python.org/whatsnew/2.6.html
+[docs]class ParamOverrides(dict):
+ """
+ A dictionary that returns the attribute of a specified object if
+ that attribute is not present in itself.
+
+ Used to override the parameters of an object.
+ """
+
+ # NOTE: Attribute names of this object block parameters of the
+ # same name, so all attributes of this object should have names
+ # starting with an underscore (_).
+
+[docs] def __init__(self,overridden,dict_,allow_extra_keywords=False):
+ """
+
+ If allow_extra_keywords is False, then all keys in the
+ supplied dict_ must match parameter names on the overridden
+ object (otherwise a warning will be printed).
+
+ If allow_extra_keywords is True, then any items in the
+ supplied dict_ that are not also parameters of the overridden
+ object will be available via the extra_keywords() method.
+ """
+ # This method should be fast because it's going to be
+ # called a lot. This _might_ be faster (not tested):
+ # def __init__(self,overridden,**kw):
+ # ...
+ # dict.__init__(self,**kw)
+ self._overridden = overridden
+ dict.__init__(self,dict_)
+
+ if allow_extra_keywords:
+ self._extra_keywords=self._extract_extra_keywords(dict_)
+ else:
+ self._check_params(dict_)
+
+ def extra_keywords(self):
+ """
+ Return a dictionary containing items from the originally
+ supplied `dict_` whose names are not parameters of the
+ overridden object.
+ """
+ return self._extra_keywords
+
+ def param_keywords(self):
+ """
+ Return a dictionary containing items from the originally
+ supplied `dict_` whose names are parameters of the
+ overridden object (i.e. not extra keywords/parameters).
+ """
+ return {key: self[key] for key in self if key not in self.extra_keywords()}
+
+ def __missing__(self,name):
+ # Return 'name' from the overridden object
+ return getattr(self._overridden,name)
+
+ def __repr__(self):
+ # As dict.__repr__, but indicate the overridden object
+ return dict.__repr__(self)+" overriding params from %s"%repr(self._overridden)
+
+ def __getattr__(self,name):
+ # Provide 'dot' access to entries in the dictionary.
+ # (This __getattr__ method is called only if 'name' isn't an
+ # attribute of self.)
+ return self.__getitem__(name)
+
+ def __setattr__(self,name,val):
+ # Attributes whose name starts with _ are set on self (as
+ # normal), but all other attributes are inserted into the
+ # dictionary.
+ if not name.startswith('_'):
+ self.__setitem__(name,val)
+ else:
+ dict.__setattr__(self,name,val)
+
+ def get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def __contains__(self, key):
+ return key in self.__dict__ or key in self._overridden.param
+
+ def _check_params(self,params):
+ """
+ Print a warning if params contains something that is not a
+ Parameter of the overridden object.
+ """
+ overridden_object_params = list(self._overridden.param)
+ for item in params:
+ if item not in overridden_object_params:
+ self.param.warning("'%s' will be ignored (not a Parameter).",item)
+
+ def _extract_extra_keywords(self,params):
+ """
+ Return any items in params that are not also
+ parameters of the overridden object.
+ """
+ extra_keywords = {}
+ overridden_object_params = list(self._overridden.param)
+ for name, val in params.items():
+ if name not in overridden_object_params:
+ extra_keywords[name]=val
+ # Could remove name from params (i.e. del params[name])
+ # so that it's only available via extra_keywords()
+ return extra_keywords
+
+
+# Helper function required by ParameterizedFunction.__reduce__
+def _new_parameterized(cls):
+ return Parameterized.__new__(cls)
+
+
+[docs]class ParameterizedFunction(Parameterized):
+ """
+ Acts like a Python function, but with arguments that are Parameters.
+
+ Implemented as a subclass of Parameterized that, when instantiated,
+ automatically invokes __call__ and returns the result, instead of
+ returning an instance of the class.
+
+ To obtain an instance of this class, call instance().
+ """
+ __abstract = True
+
+ def __str__(self):
+ return self.__class__.__name__+"()"
+
+ @bothmethod
+ def instance(self_or_cls,**params):
+ """
+ Return an instance of this class, copying parameters from any
+ existing instance provided.
+ """
+
+ if isinstance (self_or_cls,ParameterizedMetaclass):
+ cls = self_or_cls
+ else:
+ p = params
+ params = self_or_cls.param.values()
+ params.update(p)
+ params.pop('name')
+ cls = self_or_cls.__class__
+
+ inst=Parameterized.__new__(cls)
+ Parameterized.__init__(inst,**params)
+ if 'name' in params: inst.__name__ = params['name']
+ else: inst.__name__ = self_or_cls.name
+ return inst
+
+ def __new__(class_,*args,**params):
+ # Create and __call__() an instance of this class.
+ inst = class_.instance()
+ inst.param._set_name(class_.__name__)
+ return inst.__call__(*args,**params)
+
+ def __call__(self,*args,**kw):
+ raise NotImplementedError("Subclasses must implement __call__.")
+
+ def __reduce__(self):
+ # Control reconstruction (during unpickling and copying):
+ # ensure that ParameterizedFunction.__new__ is skipped
+ state = ParameterizedFunction.__getstate__(self)
+ # Here it's necessary to use a function defined at the
+ # module level rather than Parameterized.__new__ directly
+ # because otherwise pickle will find .__new__'s module to be
+ # __main__. Pretty obscure aspect of pickle.py...
+ return (_new_parameterized,(self.__class__,),state)
+
+ def _pprint(self, imports=None, prefix="\n ",unknown_value='<?>',
+ qualify=False, separator=""):
+ """
+ Same as self.param.pprint, except that X.classname(Y
+ is replaced with X.classname.instance(Y
+ """
+ r = self.param.pprint(imports,prefix,
+ unknown_value=unknown_value,
+ qualify=qualify,separator=separator)
+ classname=self.__class__.__name__
+ return r.replace(".%s("%classname,".%s.instance("%classname)
+
+
+class default_label_formatter(ParameterizedFunction):
+ "Default formatter to turn parameter names into appropriate widget labels."
+
+ capitalize = Parameter(default=True, doc="""
+ Whether or not the label should be capitalized.""")
+
+ replace_underscores = Parameter(default=True, doc="""
+ Whether or not underscores should be replaced with spaces.""")
+
+ overrides = Parameter(default={}, doc="""
+ Allows custom labels to be specified for specific parameter
+ names using a dictionary where key is the parameter name and the
+ value is the desired label.""")
+
+ def __call__(self, pname):
+ if pname in self.overrides:
+ return self.overrides[pname]
+ if self.replace_underscores:
+ pname = pname.replace('_',' ')
+ if self.capitalize:
+ pname = pname[:1].upper() + pname[1:]
+ return pname
+
+
+label_formatter = default_label_formatter
+
+
+# PARAM3_DEPRECATION: Should be able to remove this; was originally
+# adapted from OProperty from
+# infinitesque.net/articles/2005/enhancing%20Python's%20property.xhtml
+# but since python 2.6 the getter, setter, and deleter attributes of
+# a property should provide similar functionality already.
+class overridable_property:
+ """
+ The same as Python's "property" attribute, but allows the accessor
+ methods to be overridden in subclasses.
+
+ .. deprecated:: 2.0.0
+ """
+ # Delays looking up the accessors until they're needed, rather
+ # than finding them when the class is first created.
+
+ # Based on the emulation of PyProperty_Type() in Objects/descrobject.c
+
+ def __init__(self, fget=None, fset=None, fdel=None, doc=None):
+ warnings.warn(
+ message="overridable_property has been deprecated.",
+ category=_ParamDeprecationWarning,
+ stacklevel=2,
+ )
+ self.fget = fget
+ self.fset = fset
+ self.fdel = fdel
+ self.__doc__ = doc
+
+ def __get__(self, obj, objtype=None):
+ if obj is None:
+ return self
+ if self.fget is None:
+ raise AttributeError("unreadable attribute")
+ if self.fget.__name__ == '<lambda>' or not self.fget.__name__:
+ return self.fget(obj)
+ else:
+ return getattr(obj, self.fget.__name__)()
+
+ def __set__(self, obj, value):
+ if self.fset is None:
+ raise AttributeError("can't set attribute")
+ if self.fset.__name__ == '<lambda>' or not self.fset.__name__:
+ self.fset(obj, value)
+ else:
+ getattr(obj, self.fset.__name__)(value)
+
+ def __delete__(self, obj):
+ if self.fdel is None:
+ raise AttributeError("can't delete attribute")
+ if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__:
+ self.fdel(obj)
+ else:
+ getattr(obj, self.fdel.__name__)()
+
+"""
+Classes used to support string serialization of Parameters and
+Parameterized objects.
+"""
+
+import json
+import textwrap
+
+
+
+
+
+[docs]def JSONNullable(json_type):
+ "Express a JSON schema type as nullable to easily support Parameters that allow_None"
+ return {'anyOf': [ json_type, {'type': 'null'}] }
+
+
+
+[docs]class Serialization:
+ """
+ Base class used to implement different types of serialization.
+ """
+
+[docs] @classmethod
+ def schema(cls, pobj, subset=None):
+ raise NotImplementedError # noqa: unimplemented method
+
+[docs] @classmethod
+ def serialize_parameters(cls, pobj, subset=None):
+ """
+ Serialize the parameters on a Parameterized object into a
+ single serialized object, e.g. a JSON string.
+ """
+ raise NotImplementedError # noqa: unimplemented method
+
+[docs] @classmethod
+ def deserialize_parameters(cls, pobj, serialized, subset=None):
+ """
+ Deserialize a serialized object representing one or
+ more Parameters into a dictionary of parameter values.
+ """
+ raise NotImplementedError # noqa: unimplemented method
+
+[docs] @classmethod
+ def serialize_parameter_value(cls, pobj, pname):
+ """
+ Serialize a single parameter value.
+ """
+ raise NotImplementedError # noqa: unimplemented method
+
+[docs] @classmethod
+ def deserialize_parameter_value(cls, pobj, pname, value):
+ """
+ Deserialize a single parameter value.
+ """
+ raise NotImplementedError # noqa: unimplemented method
+
+
+[docs]class JSONSerialization(Serialization):
+ """
+ Class responsible for specifying JSON serialization, deserialization
+ and JSON schemas for Parameters and Parameterized classes and
+ objects.
+ """
+
+ unserializable_parameter_types = ['Callable']
+
+ json_schema_literal_types = {
+ int:'integer', float:'number', str:'string',
+ type(None): 'null'
+ }
+
+
+
+
+
+[docs] @classmethod
+ def schema(cls, pobj, safe=False, subset=None):
+ schema = {}
+ for name, p in pobj.param.objects('existing').items():
+ if subset is not None and name not in subset:
+ continue
+ schema[name] = p.schema(safe=safe)
+ if p.doc:
+ schema[name]['description'] = textwrap.dedent(p.doc).replace('\n', ' ').strip()
+ if p.label:
+ schema[name]['title'] = p.label
+ return schema
+
+[docs] @classmethod
+ def serialize_parameters(cls, pobj, subset=None):
+ components = {}
+ for name, p in pobj.param.objects('existing').items():
+ if subset is not None and name not in subset:
+ continue
+ value = pobj.param.get_value_generator(name)
+ components[name] = p.serialize(value)
+ return cls.dumps(components)
+
+[docs] @classmethod
+ def deserialize_parameters(cls, pobj, serialization, subset=None):
+ deserialized = cls.loads(serialization)
+ components = {}
+ for name, value in deserialized.items():
+ if subset is not None and name not in subset:
+ continue
+ deserialized = pobj.param[name].deserialize(value)
+ components[name] = deserialized
+ return components
+
+ # Parameter level methods
+
+ @classmethod
+ def _get_method(cls, ptype, suffix):
+ "Returns specialized method if available, otherwise None"
+ method_name = ptype.lower()+'_' + suffix
+ return getattr(cls, method_name, None)
+
+[docs] @classmethod
+ def param_schema(cls, ptype, p, safe=False, subset=None):
+ if ptype in cls.unserializable_parameter_types:
+ raise UnserializableException
+ dispatch_method = cls._get_method(ptype, 'schema')
+ if dispatch_method:
+ schema = dispatch_method(p, safe=safe)
+ else:
+ schema = {'type': ptype.lower()}
+ return JSONNullable(schema) if p.allow_None else schema
+
+[docs] @classmethod
+ def serialize_parameter_value(cls, pobj, pname):
+ value = pobj.param.get_value_generator(pname)
+ return cls.dumps(pobj.param[pname].serialize(value))
+
+[docs] @classmethod
+ def deserialize_parameter_value(cls, pobj, pname, value):
+ value = cls.loads(value)
+ return pobj.param[pname].deserialize(value)
+
+ # Custom Schemas
+
+[docs] @classmethod
+ def class__schema(cls, class_, safe=False):
+ from .parameterized import Parameterized
+ if isinstance(class_, tuple):
+ return {'anyOf': [cls.class__schema(cls_) for cls_ in class_]}
+ elif class_ in cls.json_schema_literal_types:
+ return {'type': cls.json_schema_literal_types[class_]}
+ elif issubclass(class_, Parameterized):
+ return {'type': 'object', 'properties': class_.param.schema(safe)}
+ else:
+ return {'type': 'object'}
+
+[docs] @classmethod
+ def array_schema(cls, p, safe=False):
+ if safe is True:
+ msg = ('Array is not guaranteed to be safe for '
+ 'serialization as the dtype is unknown')
+ raise UnsafeserializableException(msg)
+ return {'type': 'array'}
+
+[docs] @classmethod
+ def classselector_schema(cls, p, safe=False):
+ return cls.class__schema(p.class_, safe=safe)
+
+[docs] @classmethod
+ def dict_schema(cls, p, safe=False):
+ if safe is True:
+ msg = ('Dict is not guaranteed to be safe for '
+ 'serialization as the key and value types are unknown')
+ raise UnsafeserializableException(msg)
+ return {'type': 'object'}
+
+[docs] @classmethod
+ def date_schema(cls, p, safe=False):
+ return {'type': 'string', 'format': 'date-time'}
+
+[docs] @classmethod
+ def calendardate_schema(cls, p, safe=False):
+ return {'type': 'string', 'format': 'date'}
+
+[docs] @classmethod
+ def tuple_schema(cls, p, safe=False):
+ schema = {'type': 'array'}
+ if p.length is not None:
+ schema['minItems'] = p.length
+ schema['maxItems'] = p.length
+ return schema
+
+[docs] @classmethod
+ def number_schema(cls, p, safe=False):
+ schema = {'type': p.__class__.__name__.lower() }
+ return cls.declare_numeric_bounds(schema, p.bounds, p.inclusive_bounds)
+
+[docs] @classmethod
+ def declare_numeric_bounds(cls, schema, bounds, inclusive_bounds):
+ "Given an applicable numeric schema, augment with bounds information"
+ if bounds is not None:
+ (low, high) = bounds
+ if low is not None:
+ key = 'minimum' if inclusive_bounds[0] else 'exclusiveMinimum'
+ schema[key] = low
+ if high is not None:
+ key = 'maximum' if inclusive_bounds[1] else 'exclusiveMaximum'
+ schema[key] = high
+ return schema
+
+
+
+[docs] @classmethod
+ def numerictuple_schema(cls, p, safe=False):
+ schema = cls.tuple_schema(p, safe=safe)
+ schema['additionalItems'] = {'type': 'number'}
+ return schema
+
+[docs] @classmethod
+ def xycoordinates_schema(cls, p, safe=False):
+ return cls.numerictuple_schema(p, safe=safe)
+
+[docs] @classmethod
+ def range_schema(cls, p, safe=False):
+ schema = cls.tuple_schema(p, safe=safe)
+ bounded_number = cls.declare_numeric_bounds(
+ {'type': 'number'}, p.bounds, p.inclusive_bounds)
+ schema['additionalItems'] = bounded_number
+ return schema
+
+[docs] @classmethod
+ def list_schema(cls, p, safe=False):
+ schema = {'type': 'array'}
+ if safe is True and p.item_type is None:
+ msg = ('List without a class specified cannot be guaranteed '
+ 'to be safe for serialization')
+ raise UnsafeserializableException(msg)
+ if p.class_ is not None:
+ schema['items'] = cls.class__schema(p.item_type, safe=safe)
+ return schema
+
+[docs] @classmethod
+ def objectselector_schema(cls, p, safe=False):
+ try:
+ allowed_types = [{'type': cls.json_schema_literal_types[type(obj)]}
+ for obj in p.objects]
+ schema = {'anyOf': allowed_types}
+ schema['enum'] = p.objects
+ return schema
+ except:
+ if safe is True:
+ msg = ('ObjectSelector cannot be guaranteed to be safe for '
+ 'serialization due to unserializable type in objects')
+ raise UnsafeserializableException(msg)
+ return {}
+
+[docs] @classmethod
+ def selector_schema(cls, p, safe=False):
+ try:
+ allowed_types = [{'type': cls.json_schema_literal_types[type(obj)]}
+ for obj in p.objects.values()]
+ schema = {'anyOf': allowed_types}
+ schema['enum'] = p.objects
+ return schema
+ except:
+ if safe is True:
+ msg = ('Selector cannot be guaranteed to be safe for '
+ 'serialization due to unserializable type in objects')
+ raise UnsafeserializableException(msg)
+ return {}
+
+[docs] @classmethod
+ def listselector_schema(cls, p, safe=False):
+ if p.objects is None:
+ if safe is True:
+ msg = ('ListSelector cannot be guaranteed to be safe for '
+ 'serialization as allowed objects unspecified')
+ return {'type': 'array'}
+ for obj in p.objects:
+ if type(obj) not in cls.json_schema_literal_types:
+ msg = 'ListSelector cannot serialize type %s' % type(obj)
+ raise UnserializableException(msg)
+ return {'type': 'array', 'items': {'enum': p.objects}}
+
+[docs] @classmethod
+ def dataframe_schema(cls, p, safe=False):
+ schema = {'type': 'array'}
+ if safe is True:
+ msg = ('DataFrame is not guaranteed to be safe for '
+ 'serialization as the column dtypes are unknown')
+ raise UnsafeserializableException(msg)
+ if p.columns is None:
+ schema['items'] = {'type': 'object'}
+ return schema
+
+ mincols, maxcols = None, None
+ if isinstance(p.columns, int):
+ mincols, maxcols = p.columns, p.columns
+ elif isinstance(p.columns, tuple):
+ mincols, maxcols = p.columns
+
+ if isinstance(p.columns, int) or isinstance(p.columns, tuple):
+ schema['items'] = {'type': 'object', 'minItems': mincols,
+ 'maxItems': maxcols}
+
+ if isinstance(p.columns, list) or isinstance(p.columns, set):
+ literal_types = [{'type':el} for el in cls.json_schema_literal_types.values()]
+ allowable_types = {'anyOf': literal_types}
+ properties = {name: allowable_types for name in p.columns}
+ schema['items'] = {'type': 'object', 'properties': properties}
+
+ minrows, maxrows = None, None
+ if isinstance(p.rows, int):
+ minrows, maxrows = p.rows, p.rows
+ elif isinstance(p.rows, tuple):
+ minrows, maxrows = p.rows
+
+ if minrows is not None:
+ schema['minItems'] = minrows
+ if maxrows is not None:
+ schema['maxItems'] = maxrows
+
+ return schema
+
Short
+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/_static/copybutton.js b/_static/copybutton.js new file mode 100644 index 0000000..2ea7ff3 --- /dev/null +++ b/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = `` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = `` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_static/copybutton_funcs.js b/_static/copybutton_funcs.js new file mode 100644 index 0000000..dbe1aaa --- /dev/null +++ b/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/_static/dataframe.css b/_static/dataframe.css new file mode 100644 index 0000000..8076a48 --- /dev/null +++ b/_static/dataframe.css @@ -0,0 +1,49 @@ +table.dataframe { + margin-left: auto; + margin-right: auto; + border: none; + border-collapse: collapse; + border-spacing: 0; + font-size: 12px; + table-layout: auto; + width: 100%; +} + +.dataframe tr, +.dataframe th, +.dataframe td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} + +.dataframe tbody { + display: table-row-group; + vertical-align: middle; + border-color: inherit; +} + +.dataframe tbody tr:nth-child(odd) { + background-color: var(--pst-color-surface, #f5f5f5); + color: var(--pst-color-text-base); +} + +.dataframe thead { + border-bottom: 1px solid var(--pst-color-border); + vertical-align: bottom; +} + +.dataframe tbody tr:hover { + background-color: #e1f5fe; + color: #000000; + cursor: pointer; +} + +:host { + overflow: auto; + padding-right: 1px; +} diff --git a/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css b/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css new file mode 100644 index 0000000..eb19f69 --- /dev/null +++ b/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative}details.sd-dropdown .sd-summary-title{font-weight:700;padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary{list-style:none;padding:1em}details.sd-dropdown summary .sd-octicon.no-title{vertical-align:middle}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown summary::-webkit-details-marker{display:none}details.sd-dropdown summary:focus{outline:none}details.sd-dropdown .sd-summary-icon{margin-right:.5em}details.sd-dropdown .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary:hover .sd-summary-up svg,details.sd-dropdown summary:hover .sd-summary-down svg{opacity:1;transform:scale(1.1)}details.sd-dropdown .sd-summary-up svg,details.sd-dropdown .sd-summary-down svg{display:block;opacity:.6}details.sd-dropdown .sd-summary-up,details.sd-dropdown .sd-summary-down{pointer-events:none;position:absolute;right:1em;top:1em}details.sd-dropdown[open]>.sd-summary-title .sd-summary-down{visibility:hidden}details.sd-dropdown:not([open])>.sd-summary-title .sd-summary-up{visibility:hidden}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem} diff --git a/_static/design-tabs.js b/_static/design-tabs.js new file mode 100644 index 0000000..36b38cf --- /dev/null +++ b/_static/design-tabs.js @@ -0,0 +1,27 @@ +var sd_labels_by_text = {}; + +function ready() { + const li = document.getElementsByClassName("sd-tab-label"); + for (const label of li) { + syncId = label.getAttribute("data-sync-id"); + if (syncId) { + label.onclick = onLabelClick; + if (!sd_labels_by_text[syncId]) { + sd_labels_by_text[syncId] = []; + } + sd_labels_by_text[syncId].push(label); + } + } +} + +function onLabelClick() { + // Activate other inputs with the same sync id. + syncId = this.getAttribute("data-sync-id"); + for (label of sd_labels_by_text[syncId]) { + if (label === this) continue; + label.previousElementSibling.checked = true; + } + window.localStorage.setItem("sphinx-design-last-tab", syncId); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..527b876 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..30f4fb4 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '2.0.0rc5', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: true, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/favicon.ico b/_static/favicon.ico new file mode 100644 index 0000000..ee0844c Binary files /dev/null and b/_static/favicon.ico differ diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/gallery.css b/_static/gallery.css new file mode 100644 index 0000000..dbcf16e --- /dev/null +++ b/_static/gallery.css @@ -0,0 +1,32 @@ +ul.tab { + list-style-type: none; + padding: 0; + overflow: hidden; +} + +ul.tab li { + float: left; + padding: 0; +} + +ul.tab li label { + background: white; + padding: 6px; + border: 1px solid #ccc; + display: inline-block; +} + +ul.tab li input[type="radio"] { + opacity: 0; + width: 1px; + height: 1px; +} + +ul.tab li input[type="radio"]:checked ~ label { + background: var(--pst-color-primary); + color: white; +} + +dl.dl-horizontal { + padding-left: 50px; +} diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 0000000..19e7afd --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/_static/holoviz-icon-white.svg b/_static/holoviz-icon-white.svg new file mode 100644 index 0000000..0089fb1 --- /dev/null +++ b/_static/holoviz-icon-white.svg @@ -0,0 +1,209 @@ + + + + diff --git a/_static/icon.png b/_static/icon.png new file mode 100644 index 0000000..b9727d5 Binary files /dev/null and b/_static/icon.png differ diff --git a/_static/icon.svg b/_static/icon.svg new file mode 100644 index 0000000..ed61ab3 --- /dev/null +++ b/_static/icon.svg @@ -0,0 +1,296 @@ + + diff --git a/_static/jquery-3.6.0.js b/_static/jquery-3.6.0.js new file mode 100644 index 0000000..fc6c299 --- /dev/null +++ b/_static/jquery-3.6.0.js @@ -0,0 +1,10881 @@ +/*! + * jQuery JavaScript Library v3.6.0 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2021-03-02T17:08Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML