Skip to content

Commit

Permalink
Ensure valid key validation is enabled for all components (#493)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Oct 31, 2023
1 parent 6d59bcf commit 49762cd
Show file tree
Hide file tree
Showing 20 changed files with 72 additions and 21 deletions.
10 changes: 7 additions & 3 deletions lumen/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...]]] = []
Expand Down Expand Up @@ -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]):
Expand Down Expand Up @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion lumen/filters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions lumen/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -492,8 +493,9 @@ class Layout(Component, Viewer):

_header_format: ClassVar[str] = '<div style="font-size: 1.5em; font-weight: bold;">{header}</div>'

# 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
Expand Down
1 change: 1 addition & 0 deletions lumen/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion lumen/sources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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."
Expand Down
2 changes: 1 addition & 1 deletion lumen/tests/sample_dashboard/dashboard.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sources:
test:
type: 'file'
files: ['../sources/test.csv']
tables: ['../sources/test.csv']
pipelines:
test:
source: test
Expand Down
2 changes: 1 addition & 1 deletion lumen/tests/sample_dashboard/dashboard_legacy.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sources:
test:
type: 'file'
files: ['../sources/test.csv']
tables: ['../sources/test.csv']
targets:
- title: "Test"
source: test
Expand Down
2 changes: 1 addition & 1 deletion lumen/tests/sample_dashboard/sync_query.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ config:
sources:
test:
type: 'file'
files: ['../sources/test.csv']
tables: ['../sources/test.csv']
targets:
- title: "Test 1"
source: test
Expand Down
2 changes: 1 addition & 1 deletion lumen/tests/sample_dashboard/sync_query_filters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ config:
sources:
test:
type: 'file'
files: ['../sources/test.csv']
tables: ['../sources/test.csv']
targets:
- title: "Test"
source: test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ config:
sources:
test:
type: 'file'
files: ['../sources/test.csv']
tables: ['../sources/test.csv']
targets:
- title: "Test"
source: test
Expand Down
2 changes: 1 addition & 1 deletion lumen/tests/sample_dashboard/transform_variable.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ variables:
sources:
test:
type: 'file'
files: ['../sources/test.csv']
tables: ['../sources/test.csv']
kwargs:
parse_dates: ['D']
targets:
Expand Down
2 changes: 1 addition & 1 deletion lumen/tests/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 6 additions & 1 deletion lumen/tests/validation/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
(
Expand All @@ -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'})
5 changes: 5 additions & 0 deletions lumen/tests/validation/test_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'})
7 changes: 6 additions & 1 deletion lumen/tests/validation/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
Expand Down Expand Up @@ -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': []})
5 changes: 5 additions & 0 deletions lumen/tests/validation/test_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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': {}})
4 changes: 4 additions & 0 deletions lumen/tests/validation/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})
9 changes: 7 additions & 2 deletions lumen/tests/validation/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
{"format": "csv", "type": "default"},
),
(
{"formats": "csv"},
{},
"The Download component requires 'format' parameter to be defined",
),
(
Expand Down Expand Up @@ -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",
),
(
Expand All @@ -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'})
4 changes: 3 additions & 1 deletion lumen/transforms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -43,6 +43,8 @@ class Transform(MultiTypeComponent):

_field_params: ClassVar[List[str]] = []

_valid_keys: ClassVar[List[str] | Literal['params'] | None] = 'params'

__abstract = True

@classmethod
Expand Down
13 changes: 11 additions & 2 deletions lumen/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 49762cd

Please sign in to comment.