diff --git a/lumen/base.py b/lumen/base.py
index 1e9bb5add..163a18f5a 100644
--- a/lumen/base.py
+++ b/lumen/base.py
@@ -49,6 +49,9 @@ class Component(param.Parameterized):
# component specification
_internal_params: ClassVar[List[str]] = ['name']
+ # Deprecated parameters that are still allowed as spec keys
+ _legacy_params: ClassVar[List[str]] = []
+
# Keys that must be declared declared as a list of strings or
# tuples of strings if one of multiple must be defined.
_required_keys: ClassVar[List[str | Tuple[str, ...]]] = []
@@ -134,9 +137,10 @@ def _sync_refs(self, trigger: bool = True):
@classproperty
def _valid_keys_(cls) -> List[str] | None:
if cls._valid_keys == 'params':
- return [p for p in cls.param if p not in cls._internal_params]
+ valid = [p for p in cls.param if p not in cls._internal_params]
else:
- return cls._valid_keys
+ valid = cls._valid_keys
+ return valid if valid is None else valid + cls._legacy_params
@classmethod
def _validate_keys_(cls, spec: Dict[str, Any]):
@@ -451,7 +455,7 @@ def _valid_keys_(cls) -> List[str | Tuple[str, ...]] | None:
if valid is not None and 'type' not in valid:
valid.append('type')
- return valid
+ return valid if valid is None else valid + cls._legacy_params
@classproperty
def _base_type(cls):
diff --git a/lumen/filters/base.py b/lumen/filters/base.py
index 535739f10..af8b6d2c5 100644
--- a/lumen/filters/base.py
+++ b/lumen/filters/base.py
@@ -7,7 +7,7 @@
import types
from typing import (
- TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Tuple, Type,
+ TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Literal, Tuple, Type,
)
import bokeh # type: ignore
@@ -60,6 +60,7 @@ class Filter(MultiTypeComponent):
# Specification configuration
_internal_params: ClassVar[List[str]] = ['name', 'schema']
_requires_field: ClassVar[bool] = True
+ _valid_keys: ClassVar[List[str] | Literal['params'] | None] = 'params'
def __init__(self, **params):
super().__init__(**params)
diff --git a/lumen/layout.py b/lumen/layout.py
index c8b060a2c..49a916bb3 100644
--- a/lumen/layout.py
+++ b/lumen/layout.py
@@ -264,8 +264,9 @@ class Facet(Component):
sort = param.ListSelector(default=[], objects=[], doc="""
List of fields to sort by.""")
- _valid_keys: ClassVar[Literal['params']] = 'params'
+ # Validation attributes
_required_keys: ClassVar[List[str | Tuple[str, ...]]] = ['by']
+ _valid_keys: ClassVar[Literal['params']] = 'params'
def __init__(self, **params):
super().__init__(**params)
@@ -492,8 +493,9 @@ class Layout(Component, Viewer):
_header_format: ClassVar[str] = '
{header}
'
+ # Validation attributes
+ _legacy_params: ClassVar[List[str]] = ['table']
_required_keys: ClassVar[List[str | Tuple[str, ...]]] = ['title', 'views']
-
_valid_keys: ClassVar[List[str]] = [
'config', 'facet_layout', 'sort', # Deprecated
'layout', 'refresh_rate', 'reloadable', 'show_title', 'title', 'tsformat', 'description', # Simple
diff --git a/lumen/pipeline.py b/lumen/pipeline.py
index 7ec09de16..e1c6cbd4a 100644
--- a/lumen/pipeline.py
+++ b/lumen/pipeline.py
@@ -145,6 +145,7 @@ class Pipeline(Viewer, Component):
_internal_params: ClassVar[List[str]] = ['data', 'name', 'schema', '_stale']
_required_fields: ClassVar[List[str | Tuple[str, str]]] = [('source', 'pipeline')]
+ _valid_keys: ClassVar[List[str] | Literal['params'] | None] = 'params'
def __init__(self, *, source, table, **params):
if 'schema' not in params:
diff --git a/lumen/sources/base.py b/lumen/sources/base.py
index a31cf81a4..00b8403f0 100644
--- a/lumen/sources/base.py
+++ b/lumen/sources/base.py
@@ -14,7 +14,7 @@
from os.path import basename
from pathlib import Path
from typing import (
- TYPE_CHECKING, Any, ClassVar, Dict, List, Tuple, Type, Union,
+ TYPE_CHECKING, Any, ClassVar, Dict, List, Literal, Tuple, Type, Union,
)
from urllib.parse import quote, urlparse
@@ -170,6 +170,9 @@ class Source(MultiTypeComponent):
# Declare whether source supports SQL transforms
_supports_sql: ClassVar[bool] = False
+ # Valid keys incude all parameters (except _internal_params)
+ _valid_keys: ClassVar[List[str] | Literal['params'] | None] = 'params'
+
@property
def _reload_params(self) -> List[str]:
"List of parameters that trigger a data reload."
diff --git a/lumen/tests/sample_dashboard/dashboard.yaml b/lumen/tests/sample_dashboard/dashboard.yaml
index fadeb370e..a3fdf4599 100644
--- a/lumen/tests/sample_dashboard/dashboard.yaml
+++ b/lumen/tests/sample_dashboard/dashboard.yaml
@@ -1,7 +1,7 @@
sources:
test:
type: 'file'
- files: ['../sources/test.csv']
+ tables: ['../sources/test.csv']
pipelines:
test:
source: test
diff --git a/lumen/tests/sample_dashboard/dashboard_legacy.yaml b/lumen/tests/sample_dashboard/dashboard_legacy.yaml
index 065d31d61..b0718900f 100644
--- a/lumen/tests/sample_dashboard/dashboard_legacy.yaml
+++ b/lumen/tests/sample_dashboard/dashboard_legacy.yaml
@@ -1,7 +1,7 @@
sources:
test:
type: 'file'
- files: ['../sources/test.csv']
+ tables: ['../sources/test.csv']
targets:
- title: "Test"
source: test
diff --git a/lumen/tests/sample_dashboard/sync_query.yaml b/lumen/tests/sample_dashboard/sync_query.yaml
index 2e7dab85e..378cc580d 100644
--- a/lumen/tests/sample_dashboard/sync_query.yaml
+++ b/lumen/tests/sample_dashboard/sync_query.yaml
@@ -4,7 +4,7 @@ config:
sources:
test:
type: 'file'
- files: ['../sources/test.csv']
+ tables: ['../sources/test.csv']
targets:
- title: "Test 1"
source: test
diff --git a/lumen/tests/sample_dashboard/sync_query_filters.yaml b/lumen/tests/sample_dashboard/sync_query_filters.yaml
index 6a7b6d270..b1eb3fa0c 100644
--- a/lumen/tests/sample_dashboard/sync_query_filters.yaml
+++ b/lumen/tests/sample_dashboard/sync_query_filters.yaml
@@ -3,7 +3,7 @@ config:
sources:
test:
type: 'file'
- files: ['../sources/test.csv']
+ tables: ['../sources/test.csv']
targets:
- title: "Test"
source: test
diff --git a/lumen/tests/sample_dashboard/sync_query_filters_default.yaml b/lumen/tests/sample_dashboard/sync_query_filters_default.yaml
index 8ea7a15ff..5920e898d 100644
--- a/lumen/tests/sample_dashboard/sync_query_filters_default.yaml
+++ b/lumen/tests/sample_dashboard/sync_query_filters_default.yaml
@@ -3,7 +3,7 @@ config:
sources:
test:
type: 'file'
- files: ['../sources/test.csv']
+ tables: ['../sources/test.csv']
targets:
- title: "Test"
source: test
diff --git a/lumen/tests/sample_dashboard/transform_variable.yaml b/lumen/tests/sample_dashboard/transform_variable.yaml
index 897da41a0..dbe5d0846 100644
--- a/lumen/tests/sample_dashboard/transform_variable.yaml
+++ b/lumen/tests/sample_dashboard/transform_variable.yaml
@@ -5,7 +5,7 @@ variables:
sources:
test:
type: 'file'
- files: ['../sources/test.csv']
+ tables: ['../sources/test.csv']
kwargs:
parse_dates: ['D']
targets:
diff --git a/lumen/tests/test_dashboard.py b/lumen/tests/test_dashboard.py
index 29bec53ef..d6e5717ff 100644
--- a/lumen/tests/test_dashboard.py
+++ b/lumen/tests/test_dashboard.py
@@ -33,7 +33,7 @@ def test_dashboard_with_local_view_legacy(set_root):
def test_dashboard_from_spec():
spec = {
'sources': {
- 'test': {'type': 'file', 'files': ['./sources/test.csv']}
+ 'test': {'type': 'file', 'tables': ['./sources/test.csv']}
},
'layouts': [{
'title': 'Test',
diff --git a/lumen/tests/validation/test_filters.py b/lumen/tests/validation/test_filters.py
index cc6cfe0b9..691baa0d0 100644
--- a/lumen/tests/validation/test_filters.py
+++ b/lumen/tests/validation/test_filters.py
@@ -20,7 +20,7 @@
"The ConstantFilter component requires 'field' parameter to be defined",
),
(
- {"type": "widget", "fields": "island"},
+ {"type": "widget"},
"The WidgetFilter component requires 'field' parameter to be defined",
),
(
@@ -41,3 +41,8 @@ def test_filter_Filter(spec, msg):
else:
with pytest.raises(ValidationError, match=msg):
Filter.validate(spec)
+
+
+def test_filter_key_validation():
+ with pytest.raises(ValidationError, match="ConstantFilter component specification contained unknown key 'feld'"):
+ Filter.validate({'type': 'constant', 'feld': 'foo'})
diff --git a/lumen/tests/validation/test_layout.py b/lumen/tests/validation/test_layout.py
index dc3f20a00..b3f266dd4 100644
--- a/lumen/tests/validation/test_layout.py
+++ b/lumen/tests/validation/test_layout.py
@@ -104,3 +104,8 @@ def test_layout_Layout(spec, msg):
else:
with pytest.raises(ValidationError, match=msg):
Layout.validate(spec, context)
+
+
+def test_layout_key_validation():
+ with pytest.raises(ValidationError, match="Layout component specification contained unknown key 'src'"):
+ Layout.validate({'title': 'Table', 'src': 'penguins'})
diff --git a/lumen/tests/validation/test_pipeline.py b/lumen/tests/validation/test_pipeline.py
index c765e7a81..648aa4700 100644
--- a/lumen/tests/validation/test_pipeline.py
+++ b/lumen/tests/validation/test_pipeline.py
@@ -51,7 +51,7 @@
),
(
{"type": "file", "tables": {"penguins": "url.csv"}},
- [{"type": "widget", "fields": "species"}],
+ [{"type": "widget"}],
[{"type": "aggregate", "method": "mean", "by": ["species", "sex", "year"]}],
"The WidgetFilter component requires 'field' parameter to be defined",
),
@@ -100,3 +100,8 @@ def test_pipeline_Pipeline(source, filters, transforms, msg):
else:
with pytest.raises(ValidationError, match=msg):
Pipeline.validate(spec)
+
+
+def test_pipeline_key_validation():
+ with pytest.raises(ValidationError, match="Pipeline component specification contained unknown key 'transfomers'"):
+ Pipeline.validate({'source': None, 'transfomers': []})
diff --git a/lumen/tests/validation/test_sources.py b/lumen/tests/validation/test_sources.py
index d727be710..208546640 100644
--- a/lumen/tests/validation/test_sources.py
+++ b/lumen/tests/validation/test_sources.py
@@ -57,3 +57,8 @@ def test_source_Source(spec, context, msg):
else:
with pytest.raises(ValidationError, match=msg):
Source.validate(spec, context)
+
+
+def test_source_key_validation():
+ with pytest.raises(ValidationError, match="FileSource component specification contained unknown key 'tablas'"):
+ Source.validate({'type': 'file', 'tablas': {}})
diff --git a/lumen/tests/validation/test_transform.py b/lumen/tests/validation/test_transform.py
index 105129f4f..e4d080e90 100644
--- a/lumen/tests/validation/test_transform.py
+++ b/lumen/tests/validation/test_transform.py
@@ -29,3 +29,7 @@ def test_transforms_Transform(spec, msg):
else:
with pytest.raises(ValidationError, match=msg):
Transform.validate(spec)
+
+def test_transform_key_validation():
+ with pytest.raises(ValidationError, match="Iloc component specification contained unknown key 'strt'"):
+ Transform.validate({'type': 'iloc', 'strt': 3})
diff --git a/lumen/tests/validation/test_views.py b/lumen/tests/validation/test_views.py
index 31ef41836..164cb84fd 100644
--- a/lumen/tests/validation/test_views.py
+++ b/lumen/tests/validation/test_views.py
@@ -16,7 +16,7 @@
{"format": "csv", "type": "default"},
),
(
- {"formats": "csv"},
+ {},
"The Download component requires 'format' parameter to be defined",
),
(
@@ -46,7 +46,7 @@ def test_target_Download(spec, output):
None,
),
(
- {"type": "download", "formats": "csv"},
+ {"type": "download"},
"The DownloadView component requires 'format' parameter to be defined",
),
(
@@ -71,3 +71,8 @@ def test_target_View(spec, msg):
else:
with pytest.raises(ValidationError, match=msg):
View.validate(spec)
+
+
+def test_filter_key_validation():
+ with pytest.raises(ValidationError, match="Table component specification contained unknown key 'feld'"):
+ View.validate({'type': 'table', 'feld': 'foo'})
diff --git a/lumen/transforms/base.py b/lumen/transforms/base.py
index 04e3cf919..39768877d 100644
--- a/lumen/transforms/base.py
+++ b/lumen/transforms/base.py
@@ -7,7 +7,7 @@
import hashlib
from typing import (
- TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Tuple, Union,
+ TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Literal, Tuple, Union,
)
import numpy as np
@@ -43,6 +43,8 @@ class Transform(MultiTypeComponent):
_field_params: ClassVar[List[str]] = []
+ _valid_keys: ClassVar[List[str] | Literal['params'] | None] = 'params'
+
__abstract = True
@classmethod
diff --git a/lumen/views/base.py b/lumen/views/base.py
index c39ecc697..b32e00d78 100644
--- a/lumen/views/base.py
+++ b/lumen/views/base.py
@@ -8,7 +8,7 @@
from io import BytesIO, StringIO
from typing import (
- IO, TYPE_CHECKING, Any, ClassVar, Dict, List, Type,
+ IO, TYPE_CHECKING, Any, ClassVar, Dict, List, Literal, Type,
)
from weakref import WeakKeyDictionary
@@ -96,13 +96,22 @@ class View(MultiTypeComponent, Viewer):
# Parameters which reference fields in the table
_field_params: ClassVar[List[str]] = ['field']
+ # Optionally declares the Panel types used to render this View
+ _panel_type: ClassVar[Type[Viewable] | None] = None
+
+ # Whether this View can be rendered without a data source
_requires_source: ClassVar[bool] = True
+ # Internal cache of link_selections objects
_selections: ClassVar[WeakKeyDictionary[Document, link_selections]] = WeakKeyDictionary()
+ # Whether this source supports linked selections
_supports_selections: ClassVar[bool] = False
- _panel_type: ClassVar[Type[Viewable] | None] = None
+ # Validation attributes
+ _internal_params: ClassVar[List[str]] = ['name', 'rerender']
+ _legacy_params: ClassVar[List[str]] = ['sql_transforms', 'source', 'table', 'transforms']
+ _valid_keys: ClassVar[List[str] | Literal['params'] | None] = 'params'
__abstract = True