diff --git a/docs/getting_started/build_dashboard.md b/docs/getting_started/build_dashboard.md index 46c1f148..d0f9d65d 100644 --- a/docs/getting_started/build_dashboard.md +++ b/docs/getting_started/build_dashboard.md @@ -79,7 +79,7 @@ sources: tables: penguin_table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Penguins source: penguin_source views: @@ -114,7 +114,7 @@ sources: tables: penguin_table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Penguins source: penguin_source views: @@ -146,7 +146,7 @@ sources: tables: penguin_table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Penguins source: penguin_source views: @@ -196,7 +196,7 @@ pipelines: - type: columns columns: ['species', 'island', 'sex', 'year', 'bill_length_mm', 'bill_depth_mm'] -targets: +layouts: - title: Penguins pipeline: penguin_pipeline views: @@ -245,7 +245,7 @@ pipelines: - type: columns columns: ['species', 'island', 'sex', 'year', 'bill_length_mm', 'bill_depth_mm'] -targets: +layouts: - title: Penguins pipeline: penguin_pipeline views: @@ -272,7 +272,7 @@ targets: ## 8. Customize the appearance and behavior -The default layout we get is less than ideal for this case since it cuts off one of our plots, leaves a lot of empty space, and does not resize responsively. We can get responsive plots by adding `sizing_mode` to the target and `responsive` to the views. By changing the `layout` and `height`, we can further customize how the dashboard looks. +The default layout we get is less than ideal for this case since it cuts off one of our plots, leaves a lot of empty space, and does not resize responsively. We can get responsive plots by adding `sizing_mode` to the layout and `responsive` to the views. By changing the `layout` and `height`, we can further customize how the dashboard looks. ::::{tab-set} @@ -300,7 +300,7 @@ pipelines: - type: columns columns: ['species', 'island', 'sex', 'year', 'bill_length_mm', 'bill_depth_mm'] -targets: +layouts: - title: Penguins pipeline: penguin_pipeline layout: [[0], [1, 2]] @@ -366,7 +366,7 @@ pipelines: - type: columns columns: ['species', 'island', 'sex', 'year', 'bill_length_mm', 'bill_depth_mm'] -targets: +layouts: - title: Penguins pipeline: penguin_pipeline layout: [[0], [1, 2]] diff --git a/docs/getting_started/core_concepts.md b/docs/getting_started/core_concepts.md index 19ea72a6..2e75b366 100644 --- a/docs/getting_started/core_concepts.md +++ b/docs/getting_started/core_concepts.md @@ -45,7 +45,7 @@ To specify how you want the data to be manipulated (filtered and transformed) ::::{grid} 1 :gutter: 3 -:::{grid-item-card} `targets` +:::{grid-item-card} `layouts` To create the views (e.g. table, plot) for your dashboard ::: @@ -62,7 +62,7 @@ sources: pipelines: ...: ...: ... -targets: +layouts: - ...: ... ...: ... ``` @@ -76,7 +76,7 @@ The config section provides general settings which apply to the whole dashboard. ```{code-block} YAML config: title: The title of the overall application - layout: The layout to put the targets in ('grid', 'tabs', 'column') + layout: The layout to put the invidual (sub-)layouts in. logo: A URL or local path to an image file sync_with_url: Whether to sync app state with URL template: The template to use for the monitoring application @@ -182,12 +182,12 @@ pipelines: See the [Transform Reference](../architecture/transform) for other transform types and for the relevant parameters. -## Targets (views) +## Layouts (views) -The `targets` section defines how the dashboard is going to look. The essential structure of a `targets` section is as follows: +The `layouts` section defines how the dashboard is going to look. The essential structure of a `layouts` section is as follows: ```{code-block} YAML -targets: +layouts: - title: Dashboard title pipeline: The pipeline driving these views ...: Dashboard parameters @@ -196,16 +196,16 @@ targets: ...: View parameters ``` -:::{dropdown} Expand this dropdown for a more complex example structure of `targets` +:::{dropdown} Expand this dropdown for a more complex example structure of `layouts` :animate: fade-in-slide-down ```{code-block} yaml -targets: +layouts: - title: The title of the monitoring endpoint download: format: When specified adds a section to the sidebar allowing users to download the filtered dataset kwargs: Additional keyword arguments to pass to the pandas/dask to_ method tables: Allows declaring a subset of tables to download - pipeline: The pipeline driving the views of this target. Each View can independently declare a pipeline or all use the shared pipeline defined at the target level + pipeline: The pipeline driving the views of this layout. Each View can independently declare a pipeline or all use the shared pipeline defined at the layout level views: A list of metrics to monitor and display on the endpoint - pipeline: The Pipeline driving the View type: The type of View to use for rendering the table @@ -225,7 +225,7 @@ Each view can be of a different type, but a good starting point is the `hvPlotVi In your tutorial, the final dashboard included two `kinds` - scatter and histogram: ```{code-block} YAML -targets: +layouts: - title: Penguins pipeline: penguin_pipeline layout: [[0], [1, 2]] diff --git a/docs/getting_started/lumen_architecture.md b/docs/getting_started/lumen_architecture.md index 6ffc6359..7c394e06 100644 --- a/docs/getting_started/lumen_architecture.md +++ b/docs/getting_started/lumen_architecture.md @@ -22,8 +22,8 @@ Manipulating the data with `filters` and `transforms`. ::::{grid} 1 :gutter: 3 -:::{grid-item-card} `targets` -:link: ../user_guide/dashboard.html#targets +:::{grid-item-card} `layouts` +:link: ../user_guide/dashboard.html#layouts The presentation of the manipulated data with `views`. ::: diff --git a/docs/how_to/custom_components/local_components.md b/docs/how_to/custom_components/local_components.md index 3e5f7d0e..1660de18 100644 --- a/docs/how_to/custom_components/local_components.md +++ b/docs/how_to/custom_components/local_components.md @@ -113,7 +113,7 @@ pipelines: - type: my_module.StableSor by: island -targets: +layouts: - title: Penguins pipeline: penguin_pipeline layout: [[0, 1], [2, 3]] @@ -234,7 +234,7 @@ pipelines: - type: my_library.my_module.StableSort by: island -targets: +layouts: - title: Penguins pipeline: penguin_pipeline layout: [[0, 1], [2, 3]] diff --git a/docs/how_to/data_intake/cache.md b/docs/how_to/data_intake/cache.md index b41ad2c2..9c4196b7 100644 --- a/docs/how_to/data_intake/cache.md +++ b/docs/how_to/data_intake/cache.md @@ -9,7 +9,8 @@ This guide will show you how to locally cache data to speed up reloading from a ## Caching file -When working with large data in a non-optional file format, creating a local cache can be advantageous to save time when reloading the data. Caching a [source](../../reference/source) is done by using `cache_dir` followed by a directory. If the directory does not exist, it will be created. The data will be saved as [parquet](https://www.databricks.com/glossary/what-is-parquet) files in the cache directory. +When working with large data in a non-optional file format, creating a local cache can be advantageous to save time when reloading the data. Caching a [source](../../reference/source) is done by using `cache_dir` followed by a directory. If the directory does not exist, it will be created. The data will be saved as [parquet](https://www.databricks.com/glossary/what-is-parquet) files in the cache directory and the schema will be stored as a JSON file. + Below is an example of caching a 370 MB file that consists of almost 12 million rows. ```{warning} @@ -32,7 +33,7 @@ sources: kwargs: engine: fastparquet -targets: +layouts: - title: Table source: large_source views: @@ -73,3 +74,40 @@ Depending on the source type data caching will cache the entire table or individ ```{note} Lumen's [cache]() can be added to all [source types](../../reference/source). ``` + +## Precaching + +Sources that support caching per query can be made to pre-cache specific Filter and SQLTransform combinations. To enable pre-caching you must initialize a `Pipeline` and then either programmatically request to populate the pre-cache OR supply the pre-cache configuration as part of the YAML specification. + +A pre-cache definitions can take one of two forms + +- A dictionary containing 'filters' and 'variables' dictionaries each containing lists of values to compute a cross-product for, e.g. + +```python +{ + 'filters': { + ': ['a', 'b', 'c', ...], + ... + }, + 'variables': + : [0, 2, 4, ...], + ... + } +} +``` + +- A list containing dictionaries of explicit values for each filter and variables. + +```python +[ + { + 'filters': {: 'a'}, + 'variables': {: 0} + }, + { + 'filters': {: 'a'}, + 'variables': {: 1} + }, + ... +] +``` diff --git a/docs/how_to/data_intake/files.md b/docs/how_to/data_intake/files.md index d11abc99..2f23ee7e 100644 --- a/docs/how_to/data_intake/files.md +++ b/docs/how_to/data_intake/files.md @@ -28,7 +28,7 @@ sources: tables: local_table: local_table.csv -targets: +layouts: - title: Table source: local_source views: @@ -72,7 +72,7 @@ sources: tables: remote_table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Table source: remote_source views: diff --git a/docs/how_to/data_output/download_data.md b/docs/how_to/data_output/download_data.md index 42efc156..660a5d46 100644 --- a/docs/how_to/data_output/download_data.md +++ b/docs/how_to/data_output/download_data.md @@ -23,7 +23,7 @@ sources: tables: penguins: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Table source: penguins download: csv @@ -48,7 +48,7 @@ sources: tables: penguins: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Table source: penguins download: csv @@ -73,7 +73,7 @@ sources: tables: penguins: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Table source: penguins download: csv diff --git a/docs/how_to/data_processing/branch_pipeline.md b/docs/how_to/data_processing/branch_pipeline.md index 0077b64b..b84d8b7e 100644 --- a/docs/how_to/data_processing/branch_pipeline.md +++ b/docs/how_to/data_processing/branch_pipeline.md @@ -30,7 +30,7 @@ pipelines: - type: widget field: island -targets: +layouts: - title: Penguins sizing_mode: stretch_width views: @@ -72,7 +72,7 @@ pipelines: - type: columns columns: ['species', 'island', 'bill_length_mm', 'bill_depth_mm'] -targets: +layouts: - title: Penguins sizing_mode: stretch_width views: @@ -107,7 +107,7 @@ pipelines: - type: columns columns: ['species', 'island', 'bill_length_mm', 'bill_depth_mm'] -targets: +layouts: - title: Penguins sizing_mode: stretch_width views: diff --git a/docs/how_to/data_visualization/views.md b/docs/how_to/data_visualization/views.md index 928f8ee0..66823534 100644 --- a/docs/how_to/data_visualization/views.md +++ b/docs/how_to/data_visualization/views.md @@ -13,7 +13,7 @@ A view is the final output of a dashboard, but to be able to create a view, at l The following example source is a table containing data about individual penguins with various measurements. This source could have been [filtered](../../reference/filter/index.md) or [transformed](../../reference/transform/index.md) but is omitted to keep the example simple. -The views are located in the `targets` area and can take the form of various visual components. +The views are located in the `layouts` area and can take the form of various visual components. Lumen includes many [view types](../../reference/view/index.md) and is built so that you can easily use components from the [Holoviz ecosystem](https://holoviz.org/), such as a [scatter plot from hvPlot](https://hvplot.holoviz.org/reference/pandas/scatter.html) or an [indicator from Panel](https://panel.holoviz.org/reference/index.html#indicators). ![](../../_static/excalidraw/lumen_dashboard.png) @@ -35,7 +35,7 @@ sources: tables: penguin_table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Table source: penguin_source views: diff --git a/docs/how_to/index.md b/docs/how_to/index.md index bbcd1fec..6338ea77 100644 --- a/docs/how_to/index.md +++ b/docs/how_to/index.md @@ -5,8 +5,8 @@ Lumen's How to Guides provide step by step recipes for solving essential problem **[General](general/index)** * [Validate a specification file](general/validate) * [Build a dashboard in Python](general/pipeline_python) +* [Use variables and references](general/variables_and_references) * [Override parameter defaults]() (under construction) -* [Use variables and references]() (under construction) * [Set up authentication]() (under construction) **[Data intake](data_intake/index)** diff --git a/docs/how_to/variables_and_references.md b/docs/how_to/variables_and_references.md index c4ade0f8..4fa46e7b 100644 --- a/docs/how_to/variables_and_references.md +++ b/docs/how_to/variables_and_references.md @@ -49,7 +49,7 @@ pipelines: - type: columns columns: $variables.columns -targets: +layouts: - title: Plot pipeline: ticker_pipe views: @@ -93,7 +93,7 @@ sources: type: live urls: $csv.websites.url -targets: +layouts: - title: Status of websites source: live views: @@ -157,7 +157,7 @@ sources: tables: table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: Hello my dear friend {{ USER }} source: source views: @@ -199,7 +199,7 @@ sources: tables: table: https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv -targets: +layouts: - title: {{ shell("echo hello from the shell") }} source: source views: diff --git a/docs/user_guide/dashboard.md b/docs/user_guide/dashboard.md index 91f3ba6d..a5395995 100644 --- a/docs/user_guide/dashboard.md +++ b/docs/user_guide/dashboard.md @@ -15,7 +15,7 @@ The `config` section provides general settings which apply to the whole dashboar ```yaml config: title: The title of the overall application - layout: The layout to put the targets in ('grid', 'tabs', 'column') + layout: The layout to arrange the (sub-)layouts in ('grid', 'tabs', 'column') logo: A URL or local path to an image file sync_with_url: Whether to sync app state with URL template: The template to use for the monitoring application @@ -79,7 +79,7 @@ Once declared variables can be referenced through the rest of your dashboard spe ### `sources` -The `sources` section allows defining [Source](lumen.sources.Source) instances which can be referenced from the monitoring targets. This is useful when reusing a `Source` across multiple targets. +The `sources` section allows defining [Source](lumen.sources.Source) instances which can be referenced by the pipelines. ```yaml sources: @@ -109,7 +109,7 @@ sources: ### `pipelines` -The `pipelines` section allows declaring reusable pipelines that encapsulate a [Source](lumen.sources.Source) along with any number of [Filter](lumen.filters.Filter) and [Transform](lumen.transforms.Transform) components. A pipeline can be declared in this global section or the definition can be inlined on each Target or View. +The `pipelines` section allows declaring reusable pipelines that encapsulate a [Source](lumen.sources.Source) along with any number of [Filter](lumen.filters.Filter) and [Transform](lumen.transforms.Transform) components. A pipeline can be declared in this global section or the definition can be inlined on each Layout or View. ```yaml pipelines: @@ -123,18 +123,18 @@ pipelines: columns: [year, country, population] ``` -### `targets` +### `layouts` -The targets section defines the actual monitoring targets and therefore makes up the meat of the declaration. +The layouts section defines the actual visual representations on the page and therefore makes up the meat of the declaration. ```yaml -targets: This is the list of targets to monitor +layouts: This is the list of visual layouts to render. - title: The title of the monitoring endpoint download: format: When specified adds a section to the sidebar allowing users to download the filtered dataset kwargs: Additional keyword arguments to pass to the pandas/dask to_ method tables: Allows declaring a subset of tables to download - pipeline: The pipeline driving the views of this target. Each View can independently declare a pipeline or all use the shared pipeline defined at the target level + pipeline: The pipeline driving the views of this layout. Each View can independently declare a pipeline or all use the shared pipeline defined at the layout level views: A list of metrics to monitor and display on the endpoint - pipeline: The Pipeline driving the View type: The type of View to use for rendering the table diff --git a/examples/bikes/bikes.yaml b/examples/bikes/bikes.yaml index b4e485b0..bc4509c9 100644 --- a/examples/bikes/bikes.yaml +++ b/examples/bikes/bikes.yaml @@ -37,7 +37,7 @@ pipelines: - type: project_lnglat latitude: lat longitude: lon -targets: +layouts: - title: "Occupancy" pipeline: station_occupancy views: diff --git a/examples/nyc_taxi/nyc_taxi.yaml b/examples/nyc_taxi/nyc_taxi.yaml index c11970ed..eae09ce3 100644 --- a/examples/nyc_taxi/nyc_taxi.yaml +++ b/examples/nyc_taxi/nyc_taxi.yaml @@ -15,7 +15,7 @@ sources: - type: file regex: '{{ CATALOG_DIR }}/data' argkey: urlpath -targets: +layouts: - title: Trip Data source: nyc_taxi views: diff --git a/examples/penguins/penguins.yaml b/examples/penguins/penguins.yaml index 18b44737..170c2356 100644 --- a/examples/penguins/penguins.yaml +++ b/examples/penguins/penguins.yaml @@ -62,7 +62,27 @@ targets: layout: [[scatter], [depth_hist, length_hist, mass_hist]] sizing_mode: stretch_width height: 800 - - title: Table + - title: Table1 + pipeline: penguins + views: + table: + type: table + layout: fit_data_fill + show_index: false + theme: midnight + sizing_mode: stretch_both + sizing_mode: stretch_width + - title: Table2 + pipeline: penguins + views: + table: + type: table + layout: fit_data_fill + show_index: false + theme: midnight + sizing_mode: stretch_both + sizing_mode: stretch_width + - title: Table3 pipeline: penguins views: table: diff --git a/examples/precip/precipitation.yaml b/examples/precip/precipitation.yaml index 484d5d4a..0db7dbed 100644 --- a/examples/precip/precipitation.yaml +++ b/examples/precip/precipitation.yaml @@ -22,7 +22,7 @@ sources: precip: label: Precipitation unit: mean mm/day -targets: +layouts: - title: Southern Rockies source: rockies facet: diff --git a/examples/seattle/weather.yaml b/examples/seattle/weather.yaml index a120d8e5..d0dc339f 100644 --- a/examples/seattle/weather.yaml +++ b/examples/seattle/weather.yaml @@ -17,7 +17,7 @@ pipelines: field: date - type: widget field: weather -targets: +layouts: - title: Seattle Weather pipeline: seattle views: diff --git a/examples/user_guide/Pipeline.ipynb b/examples/user_guide/Pipeline.ipynb index dd79137a..1b5d3a73 100644 --- a/examples/user_guide/Pipeline.ipynb +++ b/examples/user_guide/Pipeline.ipynb @@ -152,6 +152,11 @@ "pipeline.add_filter('widget', field='sex')\n", "pipeline.add_filter('widget', field='year')\n", "\n", + "# Transforms\n", + "column_select = pn.widgets.MultiSelect(options=list(pipeline.data.columns), name='Column Select')\n", + "column_transform = lumen.transforms.Columns(columns=column_select)\n", + "#pipeline.add_transform(column_transform)\n", + "\n", "pipeline.data" ] }, @@ -189,7 +194,31 @@ }, { "cell_type": "markdown", - "id": "77f3033c-6933-4a01-bfda-df61bd01917e", + "id": "1c549424-a452-44e4-bb06-139d6380300b", + "metadata": {}, + "source": [ + "## Construct layouts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "326c80a6-72e5-4f5b-8545-2357abf3404b", + "metadata": {}, + "outputs": [], + "source": [ + "from lumen.views import hvPlotUIView\n", + "\n", + "from lumen.target import Target\n", + "\n", + "t = Target(views=[Table(pipeline=agg_pipeline), hvPlotUIView(pipeline=pipeline, kind='scatter', x='bill_length_mm', y='bill_depth_mm', by='species')], title='Penguin Views')\n", + "\n", + "d = lumen.Dashboard(lumen.state.to_spec(targets=[t])).show()" + ] + }, + { + "cell_type": "markdown", + "id": "a8b0659b-9b67-44fd-8c83-a9c6ed5b0408", "metadata": {}, "source": [ "## Building a dashboard\n", @@ -200,7 +229,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fec125a4-eed9-4445-a881-dd366cd40af6", + "id": "73630de4-aba3-4c65-97a0-f5f45b4db258", "metadata": {}, "outputs": [], "source": [ @@ -214,6 +243,22 @@ " ).servable()\n", ")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc031b34-0a3f-4605-9a09-5783db2a793a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e70e578c-70a0-4641-99ee-f71d32faef00", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/lumen/__init__.py b/lumen/__init__.py index f91e517f..67391dd1 100644 --- a/lumen/__init__.py +++ b/lumen/__init__.py @@ -3,9 +3,9 @@ from .config import config # noqa from .dashboard import Dashboard # noqa from .filters import Filter # noqa +from .layout import Layout # noqa from .sources import Source # noqa from .state import state # noqa -from .target import Target # noqa from .transforms import Transform # noqa from .views import View # noqa diff --git a/lumen/dashboard.py b/lumen/dashboard.py index e826d6af..e83661b9 100644 --- a/lumen/dashboard.py +++ b/lumen/dashboard.py @@ -19,11 +19,11 @@ _DEFAULT_LAYOUT, _LAYOUTS, _TEMPLATES, _THEMES, config, ) from .filters import ConstantFilter, Filter, WidgetFilter # noqa +from .layout import Layout from .panel import IconButton from .pipeline import Pipeline from .sources import RESTSource, Source # noqa from .state import state -from .target import Target from .transforms import Transform # noqa from .util import catch_and_notify, expand_spec, resolve_module_reference from .validation import ValidationError, match_suggestion_message @@ -48,7 +48,7 @@ class Config(Component): ) background_load = param.Boolean(default=False, constant=True, doc=""" - Whether to load any targets in the background.""") + Whether to load any layouts in the background.""") editable = param.Boolean(default=False, constant=True, doc=""" Whether the dashboard specification is editable from within @@ -69,7 +69,7 @@ class Config(Component): A logo to add to the theme.""") ncols = param.Integer(default=3, bounds=(1, None), constant=True, doc=""" - Number of columns to lay out targets in.""") + Number of columns to lay out layouts in.""") reloadable = param.Boolean(default=True, constant=True, doc=""" Whether to allow reloading data from source(s) using a button.""") @@ -314,13 +314,13 @@ class Dashboard(Component): defaults = param.ClassSelector(default=Defaults(), class_=Defaults, doc=""" Defaults to apply to component classes.""") - targets = param.List(default=[], item_type=Target, doc=""" - List of targets monitoring some source.""") + layouts = param.List(default=[], item_type=Layout, doc=""" + List of layouts monitoring some source.""") # Specification configuration _allows_refs = False - _valid_keys = ['variables', 'auth', 'defaults', 'config', 'sources', 'pipelines', 'targets'] + _valid_keys = ['variables', 'auth', 'defaults', 'config', 'sources', 'pipelines', 'layouts'] def __init__(self, specification=None, **params): self._load_global = params.pop('load_global', True) @@ -393,7 +393,7 @@ def _render_dashboard(self): ) self._main[:] = [alert] if isinstance(self._layout, pn.Tabs) and self.config.sync_with_url: - pn.state.location.sync(self._layout, {'active': 'target'}) + pn.state.location.sync(self._layout, {'active': 'layout'}) def _load_specification(self, from_file=False): if self._yaml_file == 'local': @@ -407,24 +407,24 @@ def _load_specification(self, from_file=False): state.spec = self.validate(load_yaml(self._yaml, **kwargs)) state.resolve_views() - def _load_target(self, target_spec): - target_spec = dict(target_spec) - if 'reloadable' not in target_spec: - target_spec['reloadable'] = self.config.reloadable - if 'auto_update' not in target_spec and self.config.auto_update is not None: - target_spec['auto_update'] = self.config.auto_update - target = Target.from_spec(target_spec) - self._rerender_watchers[target] = target.param.watch( - self._render_targets, 'rerender' + def _load_layout(self, layout_spec): + layout_spec = dict(layout_spec) + if 'reloadable' not in layout_spec: + layout_spec['reloadable'] = self.config.reloadable + if 'auto_update' not in layout_spec and self.config.auto_update is not None: + layout_spec['auto_update'] = self.config.auto_update + layout = Layout.from_spec(layout_spec) + self._rerender_watchers[layout] = layout.param.watch( + self._render_layouts, 'rerender' ) if isinstance(self._layout, pn.Tabs): - target.show_title = False - target.start() - return target + layout.show_title = False + layout.start() + return layout def _background_load(self, i, spec): - self.targets[i] = target = self._load_target(spec) - return target + self.layouts[i] = layout = self._load_layout(spec) + return layout def _materialize_specification(self, force=False): if force or self._load_global or not state.global_sources: @@ -435,36 +435,36 @@ def _materialize_specification(self, force=False): kwargs['auto_update'] = self.config.auto_update state.load_pipelines(**kwargs) if not self.auth.authorized: - self.targets = [] + self.layouts = [] return - # Clean up old targets - for target in self.targets: - if isinstance(target, Target) and target in self._rerender_watchers: - target.param.unwatch(self._rerender_watchers[target]) - del self._rerender_watchers[target] + # Clean up old layouts + for layout in self.layouts: + if isinstance(layout, Layout) and layout in self._rerender_watchers: + layout.param.unwatch(self._rerender_watchers[layout]) + del self._rerender_watchers[layout] - targets = [] - target_specs = state.spec.get('targets', []) - ntargets = len(target_specs) + layouts = [] + layout_specs = state.spec.get('layouts', []) + nlayouts = len(layout_specs) if isinstance(self._layout, pn.Tabs) and self.config.background_load: - self._thread_pool = ThreadPoolExecutor(max_workers=ntargets-1) - for i, target_spec in enumerate(target_specs): + self._thread_pool = ThreadPoolExecutor(max_workers=nlayouts-1) + for i, layout_spec in enumerate(layout_specs): if state.loading_msg: state.loading_msg.object = ( - f"Loading target {target_spec['title']!r} ({i + 1}/{ntargets})..." + f"Loading layout {layout_spec['title']!r} ({i + 1}/{nlayouts})..." ) if isinstance(self._layout, pn.Tabs) and i != self._layout.active: if self.config.background_load: - item = self._thread_pool.submit(self._background_load, i, target_spec) + item = self._thread_pool.submit(self._background_load, i, layout_spec) else: item = None self._rendered.append(False) else: - item = self._load_target(target_spec) + item = self._load_layout(layout_spec) self._rendered.append(True) - targets.append(item) - self.targets[:] = targets + layouts.append(item) + self.layouts[:] = layouts if self._thread_pool is not None: self._thread_pool.shutdown() @@ -571,21 +571,21 @@ def _populate_template(self): @catch_and_notify def _activate_filters(self, event): - target = self.targets[event.new] + layout = self.layouts[event.new] rendered = self._rendered[event.new] if not rendered: - spec = state.spec['targets'][event.new] + spec = state.spec['layouts'][event.new] self._set_loading(spec['title']) try: - if isinstance(target, Future): - target = target.result() + if isinstance(layout, Future): + layout = layout.result() else: - target = self._load_target(spec) + layout = self._load_layout(spec) except Exception as e: self._main.loading = False raise e - self.targets[event.new] = target - self._layout[event.new] = target.panels + self.layouts[event.new] = layout + self._layout[event.new] = layout.panels self._rendered[event.new] = True self._render_filters() self._main.loading = False @@ -626,27 +626,27 @@ def _open_modal(self, event): def _get_global_filters(self): views = [] filters = [] - for target in self.targets: - if target is None or isinstance(target, Future): + for layout in self.layouts: + if layout is None or isinstance(layout, Future): continue - for pipeline in target._pipelines.values(): + for pipeline in layout._pipelines.values(): for filt in pipeline.filters: - if ((all(isinstance(target, (type(None), Future)) or any(filt in p.filters for p in target._pipelines.values()) - for target in self.targets) and + if ((all(isinstance(layout, (type(None), Future)) or any(filt in p.filters for p in layout._pipelines.values()) + for layout in self.layouts) and filt.panel is not None) and filt.shared) and filt not in filters: views.append(filt.panel) filters.append(filt) if not self.config.auto_update: button = pn.widgets.Button(name='Apply Update') def update_pipelines(event): - for target in self.targets: - if target is None or isinstance(target, Future): + for layout in self.layouts: + if layout is None or isinstance(layout, Future): continue - for pipeline in target._pipelines.values(): + for pipeline in layout._pipelines.values(): pipeline.param.trigger('update') button.on_click(update_pipelines) views.append(button) - if not views or len(self.targets) == 1: + if not views or len(self.layouts) == 1: return None, None return filters, pn.Column(*views, name='Filters', sizing_mode='stretch_width') @@ -655,30 +655,30 @@ def _render_filters(self): filters = [] if global_panel is None else [global_panel] global_refs = [ref.split('.')[1] for ref in state.global_refs] self._variable_panel = self.variables.panel(global_refs) - apply_button = not self.config.auto_update or len(self.targets) == 1 + apply_button = not self.config.auto_update or len(self.layouts) == 1 if self._variable_panel is not None: filters.append(self._variable_panel) - for i, target in enumerate(self.targets): + for i, layout in enumerate(self.layouts): if isinstance(self._layout, pn.Tabs) and i != self._layout.active: continue - elif target is None or isinstance(target, Future): - spec = state.spec['targets'][i] + elif layout is None or isinstance(layout, Future): + spec = state.spec['layouts'][i] panel = pn.Column(name=spec['title']) else: - panel = target.get_filter_panel(skip=self._global_filters, apply_button=apply_button) + panel = layout.get_filter_panel(skip=self._global_filters, apply_button=apply_button) if panel is not None: filters.append(panel) self._sidebar[:] = filters - def _render_targets(self, event=None): + def _render_layouts(self, event=None): if event is not None: self._set_loading(event.obj.title) items = [] - for target, spec in zip(self.targets, state.spec.get('targets', [])): - if target is None or isinstance(target, Future): + for layout, spec in zip(self.layouts, state.spec.get('layouts', [])): + if layout is None or isinstance(layout, Future): panel = pn.layout.VSpacer(name=spec['title']) else: - panel = target.panels + panel = layout.panels items.append(panel) self._layout[:] = items self._main.loading = False @@ -717,7 +717,7 @@ def _render(self): else: active = list(range(len(self._sidebar))) self._sidebar.active = active - self._render_targets() + self._render_layouts() else: self._render_unauthorized() self._main.loading = False @@ -744,10 +744,10 @@ def _validate_sources(cls, source_specs, spec, context): return cls._validate_dict_subtypes('sources', Source, source_specs, spec, context, context['sources']) @classmethod - def _validate_targets(cls, target_specs, spec, context): - if 'targets' not in context: - context['targets'] = [] - return cls._validate_list_subtypes('targets', Target, target_specs, spec, context, context['targets']) + def _validate_layouts(cls, layout_specs, spec, context): + if 'layouts' not in context: + context['layouts'] = [] + return cls._validate_list_subtypes('layouts', Layout, layout_specs, spec, context, context['layouts']) @classmethod def _validate_variables(cls, variable_specs, spec, context): @@ -773,6 +773,10 @@ def validate(cls, spec): -------- Validated specification. """ + # Backward compatibility for old naming (targets -> layouts) + if 'targets' in spec: + spec['layouts'] = spec.pop('targets') + cls._validate_keys(spec) cls._validate_required(spec) return cls._validate_spec(spec) @@ -820,9 +824,9 @@ def to_spec(self): for name, pipeline in state.pipelines.items() } spec.update(super().to_spec(context=spec)) - spec['targets'] = [ - tspec if target is None else target - for target, tspec in zip(spec['targets'], state.spec['targets']) + spec['layouts'] = [ + tspec if layout is None else layout + for layout, tspec in zip(spec['layouts'], state.spec['layouts']) ] return {k: v for k, v in spec.items() if v != {}} diff --git a/lumen/filters/base.py b/lumen/filters/base.py index 6369cd92..c437fcd7 100644 --- a/lumen/filters/base.py +++ b/lumen/filters/base.py @@ -33,7 +33,7 @@ class Filter(MultiTypeComponent): about the data to be filtered.""") shared = param.Boolean(default=False, doc=""" - Whether the filter is shared across all targets.""") + Whether the filter is shared across all layouts.""") sync_with_url = param.Boolean(default=True, doc=""" Whether to sync the filter state with the URL parameters.""") diff --git a/lumen/target.py b/lumen/layout.py similarity index 95% rename from lumen/target.py rename to lumen/layout.py index be467691..2fef3696 100644 --- a/lumen/target.py +++ b/lumen/layout.py @@ -30,7 +30,7 @@ class Card(Viewer): """ layout = param.ClassSelector(default='column', class_=(str, list, dict), doc=""" - Defines the layout of the views in the monitor target. Can be + Defines the layout of the views in the monitor layout. Can be 'column', 'row', 'grid', 'row' or a nested list of indexes corresponding to the views, e.g. [[0, 1], [2]] will create a Column of one row containing views 0 and 1 and a second Row @@ -64,13 +64,13 @@ def _construct_layout(self): if isinstance(index, int): if isinstance(self.views, dict): raise KeyError( - f'Layout specification for {self.title!r} target used ' + f'Layout specification for {self.title!r} layout used ' f'integer index ({index}) but views were declared as a ' 'dictionary.' ) elif index >= view_size: raise IndexError( - f'Layout specification for {self.title!r} target references ' + f'Layout specification for {self.title!r} layout references ' f'out-of-bounds index ({index}) even though the maximum ' f'available index is {view_size - 1}.' ) @@ -78,7 +78,7 @@ def _construct_layout(self): elif isinstance(self.views, dict): if index not in self.views: raise KeyError( - f'Layout specification for {self.title} target references ' + f'Layout specification for {self.title} layout references ' 'unknown view {index!r}.' ) view = self.views[index] @@ -88,7 +88,7 @@ def _construct_layout(self): view = matches[0] else: raise KeyError( - f"Target could not find named view '{index}'." + f"Layout could not find named view '{index}'." ) row.append(view.panel) item.append(row) @@ -316,9 +316,9 @@ def from_spec(cls, spec, pipelines): return cls(pipelines=pipelines, **spec) -class Target(Component, Viewer): +class Layout(Component, Viewer): """ - `Target` renders one or more `View` components into a layout. + `Layout` renders one or more `View` components into a layout. Additionally it provides functionality for facetting the `View` objects by grouping the data along some dimension. @@ -336,22 +336,22 @@ class Target(Component, Viewer): facet = param.ClassSelector(class_=Facet, doc=""" The facet object determines whether and how to facet the cards - on the target.""") + on the layout.""") layout = param.ClassSelector(default='column', class_=(str, list, dict), doc=""" - Defines the layout of the views in the monitor target. Can be + Defines the layout of the views in the monitor layout. Can be 'column', 'row', 'grid', 'row' or a nested list of indexes corresponding to the views, e.g. [[0, 1], [2]] will create a Column of one row containing views 0 and 1 and a second Row containing view 2.""" ) reloadable = param.Boolean(default=True, doc=""" - Whether to allow reloading data target's source using a button.""") + Whether to allow reloading data layout's source using a button.""") show_title = param.Boolean(default=True, doc=""" Whether to show the title in Card headers.""") - title = param.String(doc="A title for this Target.") + title = param.String(doc="A title for this Layout.") refresh_rate = param.Integer(default=None, doc=""" How frequently to refresh the monitor by querying the adaptor.""") @@ -477,8 +477,8 @@ def get_filter_panel(self, skip=None, apply_button=True): # Variable controls global_refs = state.global_refs - target_refs = [ref.split('.')[1] for ref in self.refs if ref not in global_refs] - var_panel = state.variables.panel(target_refs) + layout_refs = [ref.split('.')[1] for ref in self.refs if ref not in global_refs] + var_panel = state.variables.panel(layout_refs) if var_panel is not None: views.append(var_panel) @@ -591,24 +591,24 @@ def _rerender(self, update_views=True): @classmethod def _validate_config(cls, config, spec, context): msg = ( - "Passing 'config' to a Target is deprecated use the 'facet' key " - "on the target instead." + "Passing 'config' to a Layout is deprecated use the 'facet' key " + "on the layout instead." ) return cls._deprecation(msg, 'facet', spec, config) @classmethod def _validate_sort(cls, sort, spec, context): msg = ( - "Passing 'sort' to a Target is deprecated use the 'facet' key " - "on the target instead." + "Passing 'sort' to a Layout is deprecated use the 'facet' key " + "on the layout instead." ) return cls._deprecation(msg, 'facet', spec, {'sort': sort}) @classmethod def _validate_facet_layout(cls, facet_layout, spec, context): msg = ( - "Passing 'facet_layout' to a Target is deprecated use the 'facet' key " - "on the target instead." + "Passing 'facet_layout' to a Layout is deprecated use the 'facet' key " + "on the layout instead." ) return cls._deprecation(msg, 'facet', spec, {'facet_layout': facet_layout}) @@ -628,12 +628,12 @@ def _validate_filters(cls, filter_specs, spec, context): for filter_spec in filter_specs: if filter_spec['type'] == 'facet': raise ValidationError( - 'Target facetting must be declared via the facet field of the target specification, ' + 'Layout facetting must be declared via the facet field of the layout specification, ' 'specifying filters of type \'facet\' is no longer supported.', spec, 'filters' ) if 'pipeline' in spec: raise ValidationError( - 'Target may not declare filters AND a pipeline. Please declare the filters as ' + 'Layout may not declare filters AND a pipeline. Please declare the filters as ' 'part of the pipeline. ', spec, 'filters' ) return filters @@ -642,12 +642,12 @@ def _validate_filters(cls, filter_specs, spec, context): def _validate_source(cls, source_spec, spec, context): if isinstance(source_spec, str): if source_spec not in context['sources']: - msg = f'Target specified non-existent source {source_spec!r}.' + msg = f'Layout specified non-existent source {source_spec!r}.' msg = match_suggestion_message(source_spec, list(context['sources']), msg) raise ValidationError(msg, spec, source_spec) return source_spec warnings.warn( - 'Inlining source definitions in a target is no longer supported. ' + 'Inlining source definitions in a layout is no longer supported. ' 'Please ensure you declare all sources as part of the global \'sources\' ' 'field', DeprecationWarning ) @@ -667,7 +667,7 @@ def _validate_views(cls, view_specs, spec, context): views = list(view_specs.values()) if isinstance(view_specs, dict) else view_specs if not all('pipeline' in spec for spec in views): raise ValidationError( - 'Target (or its views) must declare a source or a pipeline.', spec + 'Layout (or its views) must declare a source or a pipeline.', spec ) return view_specs @@ -678,7 +678,7 @@ def _validate_views(cls, view_specs, spec, context): @classmethod def from_spec(cls, spec, **kwargs): """ - Creates a Target object from a specification. If a Target + Creates a Layout object from a specification. If a Layout specification references an existing Source or Filter by name. Parameters @@ -686,11 +686,11 @@ def from_spec(cls, spec, **kwargs): spec : dict Specification declared as a dictionary of parameter values. kwargs: dict - Additional kwargs to pass to the Target + Additional kwargs to pass to the Layout Returns ------- - Resolved and instantiated Target object + Resolved and instantiated Layout object """ # Resolve source spec = dict(spec) @@ -779,7 +779,7 @@ def refs(self): @property def panels(self): """ - Returns a layout of the rendered View objects on this target. + Returns a layout of the rendered View objects on this layout. """ if len(self._cards) > 1: default = 'flex' if 'flex' in _LAYOUTS else 'grid' @@ -852,8 +852,8 @@ def start(self, event=None): def update(self, *events, clear_cache=True): """ - Updates the views on this target by clearing any caches and - rerendering the views on this Target. + Updates the views on this layout by clearing any caches and + rerendering the views on this Layout. """ if clear_cache: self.source.clear_cache() diff --git a/lumen/state.py b/lumen/state.py index d5456a3b..210199e7 100644 --- a/lumen/state.py +++ b/lumen/state.py @@ -130,7 +130,7 @@ def _global_filters(self): def to_spec( self, auth=None, config=None, defaults=None, - pipelines={}, sources={}, targets=[], variables=None + pipelines={}, sources={}, layouts=[], variables=None ): """ Exports the full specification of the supplied components including @@ -144,7 +144,7 @@ def to_spec( variables: lumen.variables.Variables pipelines: Dict[str, Pipeline] sources: Dict[str, Source] - targets: list[Target] + layouts: list[Layout] Returns ------- @@ -182,33 +182,33 @@ def to_spec( context['pipelines'] = {} for k, pipeline in pipelines.items(): context['pipelines'][k] = pipeline.to_spec(context=context) - if targets: - context['targets'] = [ - target.to_spec(context) for target in targets + if layouts: + context['layouts'] = [ + layout.to_spec(context) for layout in layouts ] return context @property def global_refs(self): - target_refs = [] + layout_refs = [] source_specs = self.spec.get('sources', {}) - for target in self.spec.get('targets', []): - if isinstance(target.get('source'), str): - src_ref = target['source'] - target = dict(target, source=source_specs.get(src_ref)) - target_refs.append(set(extract_refs(target, 'variables'))) + for layout in self.spec.get('layouts', []): + if isinstance(layout.get('source'), str): + src_ref = layout['source'] + layout = dict(layout, source=source_specs.get(src_ref)) + layout_refs.append(set(extract_refs(layout, 'variables'))) var_refs = extract_refs(self.spec.get('variables'), 'variables') - if target_refs: - combined_refs = set.union(*target_refs) - merged_refs = set.intersection(*target_refs) + if layout_refs: + combined_refs = set.union(*layout_refs) + merged_refs = set.intersection(*layout_refs) else: combined_refs, merged_refs = set(), set() return list(merged_refs | (set(var_refs) - combined_refs)) def load_global_sources(self, clear_cache=True): """ - Loads global sources shared across all targets. + Loads global sources shared across all layouts. """ from .sources import Source for name, source_spec in self.spec.get('sources', {}).items(): @@ -288,8 +288,8 @@ def load_source(self, name, source_spec): def resolve_views(self): from .views import View exts = [] - for target in self.spec.get('targets', []): - views = target.get('views', []) + for layout in self.spec.get('layouts', []): + views = layout.get('views', []) if isinstance(views, dict): views = list(views.values()) for view in views: diff --git a/lumen/tests/test_dashboard.py b/lumen/tests/test_dashboard.py index 0dc4b6d1..123ec0d3 100644 --- a/lumen/tests/test_dashboard.py +++ b/lumen/tests/test_dashboard.py @@ -15,8 +15,8 @@ def test_dashboard_with_local_view(set_root): root = pathlib.Path(__file__).parent / 'sample_dashboard' set_root(str(root)) dashboard = Dashboard(str(root / 'dashboard.yml')) - target = dashboard.targets[0] - view = View.from_spec(target.views[0], target.source, []) + layout = dashboard.layouts[0] + view = View.from_spec(layout.views[0], layout.source, []) assert isinstance(view, config._modules[str(root / 'views.py')].TestView) def test_dashboard_from_spec(): @@ -24,7 +24,7 @@ def test_dashboard_from_spec(): 'sources': { 'test': {'type': 'file', 'files': ['./sources/test.csv']} }, - 'targets': [{ + 'layouts': [{ 'title': 'Test', 'source': 'test', 'views': [{'table': 'test', 'type': 'table'}], @@ -33,15 +33,15 @@ def test_dashboard_from_spec(): dashboard = Dashboard(spec, root=str(pathlib.Path(__file__).parent)) dashboard._render_dashboard() assert state.spec == spec - target = dashboard.targets[0] - view = View.from_spec(target.views[0], target.source, []) + layout = dashboard.layouts[0] + view = View.from_spec(layout.views[0], layout.source, []) assert view.view_type == 'table' def test_dashboard_from_spec_invalid(): with pytest.raises(ValidationError): Dashboard({'foo': 'bar'}) -def test_dashboard_reload_target(set_root): +def test_dashboard_reload_layout(set_root): root = pathlib.Path(__file__).parent / 'sample_dashboard' set_root(str(root)) dashboard = Dashboard(str(root / 'dashboard.yml')) @@ -59,8 +59,8 @@ def test_dashboard_with_url_sync(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'sync_query.yml')) dashboard._render_dashboard() - assert pn.state.location.search == '?target=0' - pn.state.location.search = '?target=1' + assert pn.state.location.search == '?layout=0' + pn.state.location.search = '?layout=1' assert dashboard._layout.active == 1 def test_dashboard_with_url_sync_filters(set_root, document): @@ -68,8 +68,8 @@ def test_dashboard_with_url_sync_filters(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'sync_query_filters.yml')) dashboard._render_dashboard() - target = dashboard.targets[0] - f1, f2 = list(target._pipelines.values())[0].filters + layout = dashboard.layouts[0] + f1, f2 = list(layout._pipelines.values())[0].filters f1.value = (0.1, 0.7) assert pn.state.location.search == '?A=%5B0.1%2C+0.7%5D' pn.state.location.search = '?A=%5B0.3%2C+0.8%5D' @@ -84,10 +84,10 @@ def test_dashboard_with_sql_source_and_transforms(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'sql_dashboard.yml')) dashboard._render_dashboard() - target = dashboard.targets[0] - target.update() + layout = dashboard.layouts[0] + layout.update() - table = target._cards[0]._card[0][0] + table = layout._cards[0]._card[0][0] expected = pd._testing.makeMixedDataFrame() pd.testing.assert_frame_equal(table.value, expected) @@ -100,10 +100,10 @@ def test_dashboard_with_transform_variable(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'transform_variable.yml')) dashboard._render_dashboard() - target = dashboard.targets[0] - target.update() + layout = dashboard.layouts[0] + layout.update() - table = target._cards[0]._card[0][0] + table = layout._cards[0]._card[0][0] expected = pd._testing.makeMixedDataFrame() pd.testing.assert_frame_equal(table.value, expected) @@ -116,10 +116,10 @@ def test_dashboard_with_source_variable(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'source_variable.yml')) dashboard._render_dashboard() - target = dashboard.targets[0] - target.update() + layout = dashboard.layouts[0] + layout.update() - table = target._cards[0]._card[0][0] + table = layout._cards[0]._card[0][0] expected = pd._testing.makeMixedDataFrame() pd.testing.assert_frame_equal(table.value, expected) @@ -132,10 +132,10 @@ def test_dashboard_with_nested_source_variable(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'source_nested_variable.yml')) dashboard._render_dashboard() - target = dashboard.targets[0] - target.update() + layout = dashboard.layouts[0] + layout.update() - table = target._cards[0]._card[0][0] + table = layout._cards[0]._card[0][0] expected = pd._testing.makeMixedDataFrame() pd.testing.assert_frame_equal(table.value, expected) @@ -148,10 +148,10 @@ def test_dashboard_with_view_variable(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'view_variable.yml')) dashboard._render_dashboard() - target = dashboard.targets[0] - target.update() + layout = dashboard.layouts[0] + layout.update() - table = target._cards[0]._card[0][0] + table = layout._cards[0]._card[0][0] assert table.page_size == 20 @@ -164,10 +164,10 @@ def test_dashboard_with_view_and_transform_variable(set_root, document): set_root(str(root)) dashboard = Dashboard(str(root / 'view_transform_variable.yaml')) dashboard._render_dashboard() - target = dashboard.targets[0] - target.update() + layout = dashboard.layouts[0] + layout.update() - plot = target._cards[0]._card[0][0] + plot = layout._cards[0]._card[0][0] assert plot.object.vdims == ['Z'] @@ -175,6 +175,6 @@ def test_dashboard_with_view_and_transform_variable(set_root, document): assert plot.object.vdims == ['Z'] - list(target._pipelines.values())[0].param.trigger('update') + list(layout._pipelines.values())[0].param.trigger('update') assert plot.object.vdims == ['Y'] diff --git a/lumen/tests/test_target.py b/lumen/tests/test_layout.py similarity index 86% rename from lumen/tests/test_target.py rename to lumen/tests/test_layout.py index 31d9e2ac..f190d2be 100644 --- a/lumen/tests/test_target.py +++ b/lumen/tests/test_layout.py @@ -11,10 +11,10 @@ from panel.param import Param from panel.widgets import Tabulator +from lumen.layout import Layout from lumen.pipeline import Pipeline from lumen.sources import DerivedSource, FileSource from lumen.state import state -from lumen.target import Target from lumen.transforms import Astype from lumen.views import View @@ -28,15 +28,15 @@ def test_view_controls(set_root): 'x': 'A', 'y': 'B', 'kind': 'scatter' } } - target = Target.from_spec({'source': source, 'views': views}) + layout = Layout.from_spec({'source': source, 'views': views}) - filter_panel = target.get_filter_panel() + filter_panel = layout.get_filter_panel() param_pane = filter_panel[0][0] assert isinstance(param_pane, Param) assert param_pane.parameters == ['x', 'y'] - assert len(target._cards) == 1 - card = target._cards[0] + assert len(layout._cards) == 1 + card = layout._cards[0] hv_pane = card._card[0][0] isinstance(hv_pane.object, hv.Scatter) assert hv_pane.object.kdims == ['A'] @@ -69,15 +69,15 @@ def test_transform_controls(set_root): doc = Document() with set_curdoc(doc): - target = Target.from_spec({'views': views, 'source': source}) - filter_panel = target.get_filter_panel() + layout = Layout.from_spec({'views': views, 'source': source}) + filter_panel = layout.get_filter_panel() param_pane = filter_panel[0][0] assert isinstance(param_pane, Param) assert param_pane.parameters == ['by'] - assert len(target._cards) == 1 - card = target._cards[0] + assert len(layout._cards) == 1 + card = layout._cards[0] hv_pane = card._card[0][0] isinstance(hv_pane.object, hv.Scatter) assert hv_pane.object.kdims == ['A'] @@ -111,15 +111,15 @@ def test_view_controls_facetted(set_root): 'facet': {'by': 'C'}, 'views': views } - target = Target.from_spec(spec, sources={'test': source}) + layout = Layout.from_spec(spec, sources={'test': source}) - filter_panel = target.get_filter_panel() + filter_panel = layout.get_filter_panel() param_pane = filter_panel[3][0] assert isinstance(param_pane, Param) assert param_pane.parameters == ['x', 'y'] - assert len(target._cards) == 5 - for card in target._cards: + assert len(layout._cards) == 5 + for card in layout._cards: hv_pane = card._card[0][0] isinstance(hv_pane.object, hv.Scatter) assert hv_pane.object.kdims == ['A'] @@ -128,7 +128,7 @@ def test_view_controls_facetted(set_root): param_pane._widgets['x'].value = 'C' param_pane._widgets['y'].value = 'D' - for card in target._cards: + for card in layout._cards: hv_pane = card._card[0][0] isinstance(hv_pane.object, hv.Scatter) assert hv_pane.object.kdims == ['C'] @@ -163,15 +163,15 @@ def test_transform_controls_facetted(set_root): doc = Document() with set_curdoc(doc): state.sources['test'] = derived - target = Target.from_spec(spec, sources={'test': derived}) - filter_panel = target.get_filter_panel() + layout = Layout.from_spec(spec, sources={'test': derived}) + filter_panel = layout.get_filter_panel() param_pane = filter_panel[3][0] assert isinstance(param_pane, Param) assert param_pane.parameters == ['ascending'] - assert len(target._cards) == 2 - card1, card2 = target._cards + assert len(layout._cards) == 2 + card1, card2 = layout._cards hv_pane1 = card1._card[0][0] isinstance(hv_pane1.object, hv.Scatter) assert hv_pane1.object.kdims == ['D'] @@ -222,12 +222,12 @@ def test_layout_view(set_root, layout, error): if error is not None: with pytest.raises(error): - Target.from_spec(spec, sources={'test': derived}) + Layout.from_spec(spec, sources={'test': derived}) else: - Target.from_spec(spec, sources={'test': derived}) + Layout.from_spec(spec, sources={'test': derived}) -def test_target_constructor_with_views_instance_list(set_root): +def test_layout_constructor_with_views_instance_list(set_root): set_root(str(Path(__file__).parent)) source = FileSource(tables={'test': 'sources/test.csv'}) view1 = View.from_spec({ @@ -237,8 +237,8 @@ def test_target_constructor_with_views_instance_list(set_root): view2 = View.from_spec({ 'type': 'table', 'table': 'test' }, source=source) - target = Target(views=[view1, view2]) - layout = target.panels + layout = Layout(views=[view1, view2]) + layout = layout.panels assert isinstance(layout, Column) assert len(layout) == 1 @@ -249,7 +249,7 @@ def test_target_constructor_with_views_instance_list(set_root): assert isinstance(layout[0][0][0], HoloViews) assert isinstance(layout[0][0][1], Tabulator) -def test_target_constructor_with_views_instance_dict(set_root): +def test_layout_constructor_with_views_instance_dict(set_root): set_root(str(Path(__file__).parent)) source = FileSource(tables={'test': 'sources/test.csv'}) view1 = View.from_spec({ @@ -259,8 +259,8 @@ def test_target_constructor_with_views_instance_dict(set_root): view2 = View.from_spec({ 'type': 'table', 'table': 'test' }, source=source) - target = Target(views={'hv': view1, 'table': view2}, layout=[['hv'], ['table']]) - layout = target.panels + layout = Layout(views={'hv': view1, 'table': view2}, layout=[['hv'], ['table']]) + layout = layout.panels assert isinstance(layout, Column) assert len(layout) == 1 @@ -273,7 +273,7 @@ def test_target_constructor_with_views_instance_dict(set_root): assert isinstance(layout[0][0][1], Row) assert isinstance(layout[0][0][1][0], Tabulator) -def test_target_to_spec_view_list(set_root): +def test_layout_to_spec_view_list(set_root): set_root(str(Path(__file__).parent)) source = FileSource(tables={'test': 'sources/test.csv'}) pipeline = Pipeline(source=source, table='test') @@ -281,10 +281,10 @@ def test_target_to_spec_view_list(set_root): 'type': 'hvplot', 'x': 'A', 'y': 'B', 'kind': 'scatter' }, pipeline=pipeline) view2 = View.from_spec({'type': 'table'}, pipeline=pipeline) - target = Target(views=[view1, view2], title='Plots') + layout = Layout(views=[view1, view2], title='Plots') context = {} - spec = target.to_spec(context) + spec = layout.to_spec(context) assert context == { 'sources': { source.name: { @@ -315,13 +315,13 @@ def test_target_to_spec_view_list(set_root): } -def test_target_to_spec_with_views_instance_dict(set_root): +def test_layout_to_spec_with_views_instance_dict(set_root): set_root(str(Path(__file__).parent)) source = FileSource(tables={'test': 'sources/test.csv'}) pipeline = Pipeline(source=source, table='test') view1 = View.from_spec({'type': 'hvplot', 'x': 'A', 'y': 'B', 'kind': 'scatter'}, pipeline=pipeline) view2 = View.from_spec({'type': 'table'}, pipeline=pipeline) - target = Target( + layout = Layout( views={'hv': view1, 'table': view2}, layout=[['hv'], ['table']], title="Test" @@ -329,7 +329,7 @@ def test_target_to_spec_with_views_instance_dict(set_root): assert view1.pipeline is view2.pipeline context = {} - spec = target.to_spec(context) + spec = layout.to_spec(context) assert context == { 'sources': { source.name: { diff --git a/lumen/tests/validation/test_dashboard.py b/lumen/tests/validation/test_dashboard.py index eac0285b..05996bff 100644 --- a/lumen/tests/validation/test_dashboard.py +++ b/lumen/tests/validation/test_dashboard.py @@ -5,7 +5,7 @@ @pytest.mark.parametrize( - "sources,targets,msg", + "sources,layouts,msg", ( ( {"penguins": {"type": "file", "tables": {"penguins": "url.com"}}}, @@ -15,17 +15,17 @@ ( {"penguin": {"type": "file", "tables": {"penguins": "url.com"}}}, [{"title": "Table", "source": "penguins", "views": []}], - "Target specified non-existent source 'penguins'", + "Layout specified non-existent source 'penguins'", ), ( {"penguins": {"type": "file", "tables": {"penguins": "url.com"}}}, [{"title": "Table", "source": "penguin", "views": []}], - "Target specified non-existent source 'penguin'", + "Layout specified non-existent source 'penguin'", ), ( {"penguins": {"type": "file", "tables": {"penguins": "url.com"}}}, [{"title": "Table", "source": "penguins", "view": []}], - "Target component specification contained unknown key 'view'", + "Layout component specification contained unknown key 'view'", ), ( {"penguins": {"tables": {"penguins": "url.com"}}}, @@ -35,20 +35,20 @@ ( {"penguins": {"type": "file", "tables": {"penguins": "url.com"}}}, [{"source": "penguins", "views": []}], - "The Target component requires 'title' parameter to be defined", + "The Layout component requires 'title' parameter to be defined", ), ), ids=[ "correct", - "missing_target1", - "missing_target2", + "missing_layout1", + "missing_layout2", "missing_view", "missing_type", "missing_title", ], ) -def test_dashboard_Dashboard(sources, targets, msg): - spec = {"sources": sources, "targets": targets} +def test_dashboard_Dashboard(sources, layouts, msg): + spec = {"sources": sources, "layouts": layouts} if msg is None: assert Dashboard.validate(spec.copy()) == spec diff --git a/lumen/tests/validation/test_target.py b/lumen/tests/validation/test_layout.py similarity index 83% rename from lumen/tests/validation/test_target.py rename to lumen/tests/validation/test_layout.py index 18758802..adafa4b7 100644 --- a/lumen/tests/validation/test_target.py +++ b/lumen/tests/validation/test_layout.py @@ -1,6 +1,6 @@ import pytest -from lumen.target import Download, Facet, Target +from lumen.layout import Download, Facet, Layout from lumen.validation import ValidationError @@ -26,7 +26,7 @@ ), ids=["correct", "unknown_key", "missing_required", "wrong_type"], ) -def test_target_Facet(spec, msg): +def test_layout_Facet(spec, msg): if msg is None: Facet.validate(spec) @@ -57,7 +57,7 @@ def test_target_Facet(spec, msg): ), ids=["correct1", "correct2", "missing_required", "wrong_format"], ) -def test_target_Download(spec, msg): +def test_layout_Download(spec, msg): if msg is None: if isinstance(spec, str): assert Download.validate(spec) == {"format": "csv"} @@ -78,15 +78,15 @@ def test_target_Download(spec, msg): ), ( {"title": "Table", "source": "penguin", "views": []}, - "Target specified non-existent source 'penguin'", + "Layout specified non-existent source 'penguin'", ), ( {"title": "Table", "source": "penguins"}, - "The Target component requires 'views' parameter to be defined", + "The Layout component requires 'views' parameter to be defined", ), ( {"source": "penguins", "views": []}, - "The Target component requires 'title' parameter to be defined", + "The Layout component requires 'title' parameter to be defined", ), ), ids=[ @@ -96,15 +96,15 @@ def test_target_Download(spec, msg): "missing_title", ], ) -def test_target_Target(spec, msg): +def test_layout_Layout(spec, msg): context = { "sources": {"penguins": {}}, - "targets": [], + "layouts": [], } if msg is None: - assert Target.validate(spec.copy(), context) == spec + assert Layout.validate(spec.copy(), context) == spec else: with pytest.raises(ValidationError, match=msg): - Target.validate(spec, context) + Layout.validate(spec, context) diff --git a/lumen/ui/builder.py b/lumen/ui/builder.py index 82c80f5c..ffd3b310 100644 --- a/lumen/ui/builder.py +++ b/lumen/ui/builder.py @@ -16,10 +16,10 @@ from .config import ConfigEditor from .dashboard import DashboardGallery from .launcher import LauncherGallery +from .layouts import LayoutEditor, LayoutGallery, LayoutGalleryItem from .pipeline import PipelineGallery from .sources import SourceGallery from .state import state -from .targets import TargetEditor, TargetGallery, TargetGalleryItem from .variables import VariablesEditor from .views import ViewEditor, ViewGallery, ViewGalleryItem @@ -36,14 +36,14 @@ class Builder(param.Parameterized): def __init__(self, **params): path = params['component_dir'] - dash_params, launcher_params, pipeline_params, source_params, target_params, var_params, view_params = ( + dash_params, launcher_params, pipeline_params, source_params, layout_params, var_params, view_params = ( {}, {}, {}, {}, {}, {}, {} ) dash_params['path'] = os.path.join(path, 'dashboards') launcher_params['path'] = os.path.join(path, 'launchers') pipeline_params['path'] = os.path.join(path, 'pipelines') source_params['path'] = os.path.join(path, 'sources') - target_params['path'] = os.path.join(path, 'targets') + layout_params['path'] = os.path.join(path, 'layouts') var_params['path'] = os.path.join(path, 'variables') view_params['path'] = os.path.join(path, 'views') @@ -61,11 +61,11 @@ def __init__(self, **params): state.sources = self.sources = SourceGallery(spec=self.spec['sources'], **source_params) state.pipelines = self.pipelines = PipelineGallery(spec=self.spec['pipelines'], **pipeline_params) state.views = self.views = ViewGallery(**view_params) - state.targets = self.targets = TargetGallery(spec=self.spec['targets'], **target_params) + state.layouts = self.layouts = LayoutGallery(spec=self.spec['layouts'], **layout_params) self.launcher = LauncherGallery(builder=self, **launcher_params) self.wizard = Wizard(items=[ self.welcome, self.config, self.variables, self.sources, - self.pipelines, self.views, self.targets, self.launcher + self.pipelines, self.views, self.layouts, self.launcher ], sizing_mode='stretch_both') preview = pn.widgets.Button(name='Preview', width=100) @@ -129,9 +129,9 @@ def _update_spec(self): self.pipelines.param.trigger('spec') views, view_gallery = [], {} - targets, target_items = [], {} - for target in self.spec['targets']: - view_specs = target['views'] + layouts, layout_items = [], {} + for layout in self.spec['layouts']: + view_specs = layout['views'] if isinstance(view_specs, list): specs = {} for view in view_specs: @@ -144,25 +144,25 @@ def _update_spec(self): specs[name] = view view_specs = specs view_specs = view_specs.items() - target_views = [] + layout_views = [] for name, view in view_specs: view = dict(view) view_type = view.get('type') view_editor = ViewEditor( - view_type=view_type, name=name, spec=view, pipeline=target.get('pipeline') + view_type=view_type, name=name, spec=view, pipeline=layout.get('pipeline') ) view_gallery[name] = ViewGalleryItem( editor=view_editor, name=name, selected=True, spec=view ) views.append(view_editor) - target_views.append(name) + layout_views.append(name) - target_editor = TargetEditor(spec=target, views=target_views) - item = TargetGalleryItem(spec=target, editor=target_editor) - targets.append(target_editor) - target_items[f'target{uuid.uuid4().hex}'] = item + layout_editor = LayoutEditor(spec=layout, views=layout_views) + item = LayoutGalleryItem(spec=layout, editor=layout_editor) + layouts.append(layout_editor) + layout_items[f'layout{uuid.uuid4().hex}'] = item - self.targets.param.set_param(targets=targets, items=target_items) + self.layouts.param.set_param(layouts=layouts, items=layout_items) self.views.param.set_param(views=views, items=view_gallery) self.welcome.ready = True self.wizard.loading = False diff --git a/lumen/ui/dashboard.py b/lumen/ui/dashboard.py index 0152c38f..a40b5dd8 100644 --- a/lumen/ui/dashboard.py +++ b/lumen/ui/dashboard.py @@ -139,7 +139,7 @@ def _yaml_upload(self, event): return def _create_new(self, event): - self.spec = {'sources': {}, 'targets': []} + self.spec = {'sources': {}, 'layouts': []} def _selected(self, event): self.spec = event.obj.spec diff --git a/lumen/ui/targets.py b/lumen/ui/layouts.py similarity index 83% rename from lumen/ui/targets.py rename to lumen/ui/layouts.py index 1dc0929d..990cd8f7 100644 --- a/lumen/ui/targets.py +++ b/lumen/ui/layouts.py @@ -13,8 +13,8 @@ from .state import state -class TargetEditor(ReactiveHTML): - "Select the views on this target and declare a layout." +class LayoutEditor(ReactiveHTML): + "Select the views on this layout and declare a layout." spec = param.Dict(default={}) @@ -32,7 +32,7 @@ class TargetEditor(ReactiveHTML): view_select = param.Parameter() _template = """ - {{ title }} Target + {{ title }} Layout

{{ __doc__ }}

@@ -80,7 +80,7 @@ def __init__(self, **params): @property def thumbnail(self): - return ASSETS_DIR / 'target.png' + return ASSETS_DIR / 'layout.png' def _construct_layout(self, layout_spec): layout_kwargs = {'sizing_mode': 'stretch_both'} @@ -140,9 +140,9 @@ def _update_layout(self): self.layout = layout -class TargetGalleryItem(GalleryItem): +class LayoutGalleryItem(GalleryItem): - editor = param.ClassSelector(class_=TargetEditor, precedence=-1) + editor = param.ClassSelector(class_=LayoutEditor, precedence=-1) selected = param.Boolean(default=True) @@ -163,12 +163,12 @@ def _open_modal(self, event): super()._open_modal(event) -class TargetGallery(WizardItem, Gallery): +class LayoutGallery(WizardItem, Gallery): "Add, select and configure layout groups to add to your dashboard." spec = param.ClassSelector(class_=(dict, list), default=[]) - targets = param.List(precedence=-1) + layouts = param.List(precedence=-1) _template = """ Layout groups @@ -176,7 +176,7 @@ class TargetGallery(WizardItem, Gallery): {{ __doc__ }}

{% for item in items.values() %} - + ${item} {% endfor %} @@ -191,29 +191,29 @@ class TargetGallery(WizardItem, Gallery): def __init__(self, **params): super().__init__(**params) - self._editor = TargetsEditor() - self._save_button = pn.widgets.Button(name='Save target') - self._save_button.on_click(self._save_targets) + self._editor = LayoutsEditor() + self._save_button = pn.widgets.Button(name='Save layout') + self._save_button.on_click(self._save_layouts) self._modal_content = [self._editor, self._save_button] - def _save_targets(self, event): - for target in self._editor.targets: - item = TargetGalleryItem( - name=target.title, spec=target.spec, selected=True, - editor=target, thumbnail=target.thumbnail + def _save_layouts(self, event): + for layout in self._editor.layouts: + item = LayoutGalleryItem( + name=layout.title, spec=layout.spec, selected=True, + editor=layout, thumbnail=layout.thumbnail ) - self.items[target.title] = item - self.targets.append(target) - self.spec.append(target.spec) + self.items[layout.title] = item + self.layouts.append(layout) + self.spec.append(layout.spec) self.param.trigger('items') - self.param.trigger('targets') - self._editor.targets = [] + self.param.trigger('layouts') + self._editor.layouts = [] state.template.close_modal() -class TargetsEditor(WizardItem): +class LayoutsEditor(WizardItem): """ - Add and configure your monitoring targets. + Add and configure your monitoring layouts. """ title = param.String(default="") @@ -224,28 +224,28 @@ class TargetsEditor(WizardItem): source = param.String() - targets = param.List([], precedence=-1) + layouts = param.List([], precedence=-1) title = param.String(default='') _template = """ Layout editor

{{ __doc__ }}

- + +
-
- {% for target in targets %} -
${target}
+
+ {% for layout in layouts %} +
${layout}
{% endfor %}
""" - _dom_events = {'target-title': ['keyup']} + _dom_events = {'layout-title': ['keyup']} def __init__(self, **params): super().__init__(**params) @@ -257,10 +257,10 @@ def _update_sources(self, event): if not self.source and self.sources: self.source = self.sources[0] - def _add_target(self, event): + def _add_layout(self, event): spec = {'title': self.title} - editor = TargetEditor(spec=spec, title=self.title) + editor = LayoutEditor(spec=spec, title=self.title) self.spec.append(spec) - self.targets.append(editor) - self.param.trigger('targets') + self.layouts.append(editor) + self.param.trigger('layouts') self.title = '' diff --git a/lumen/ui/main.py b/lumen/ui/main.py index ee4077ef..15667a5a 100644 --- a/lumen/ui/main.py +++ b/lumen/ui/main.py @@ -43,11 +43,11 @@ def main(): (path / 'launchers').mkdir(parents=True, exist_ok=True) (path / 'pipelines').mkdir(parents=True, exist_ok=True) (path / 'sources').mkdir(parents=True, exist_ok=True) - (path / 'targets').mkdir(parents=True, exist_ok=True) + (path / 'layouts').mkdir(parents=True, exist_ok=True) (path / 'variables').mkdir(parents=True, exist_ok=True) (path / 'views').mkdir(parents=True, exist_ok=True) state.modal = pn.Column(sizing_mode='stretch_both') - state.spec = {'config': {}, 'sources': {}, 'targets': [], 'variables': {}} + state.spec = {'config': {}, 'sources': {}, 'layouts': [], 'variables': {}} state.template = FastListTemplate(theme='dark', title='Lumen Builder') builder = Builder( template=state.template, spec=state.spec, modal=state.modal, **params diff --git a/lumen/ui/views.py b/lumen/ui/views.py index d8deb66a..96d2ac72 100644 --- a/lumen/ui/views.py +++ b/lumen/ui/views.py @@ -153,7 +153,7 @@ def description(self): elif 'pipeline' in self.spec: source = f"{self.spec['pipeline']!r} pipeline" else: - source = 'target source.' + source = 'layout source.' return f"A {self.view_type} view of the {source}." def render(self):