Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register component suites by importing. #444

Merged
merged 15 commits into from
Nov 6, 2018
Merged
3 changes: 2 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ confidence=
disable=fixme,
missing-docstring,
invalid-name,
too-many-lines
too-many-lines,
old-style-class
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.29.0 - 2018-11-06
## Added
- Added component namespaces registry, collect the resources needed by component library when they are imported instead of crawling the layout. [#444](https://github.com/plotly/dash/pull/444)

## 0.28.7 - 2018-11-05
## Fixed
- Component generation now uses the same prop name black list in all supported Python versions. Closes [#361](https://github.com/plotly/dash/issues/361). [#450](https://github.com/plotly/dash/pull/450)
Expand Down
45 changes: 45 additions & 0 deletions dash/development/base_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,53 @@
import copy
import os
import inspect
import abc
import sys
import six

from ._all_keywords import kwlist


# pylint: disable=no-init,too-few-public-methods
class ComponentRegistry:
"""Holds a registry of the namespaces used by components."""

registry = set()
__dist_cache = {}

@classmethod
def get_resources(cls, resource_name):
cached = cls.__dist_cache.get(resource_name)

if cached:
return cached

cls.__dist_cache[resource_name] = resources = []

for module_name in cls.registry:
module = sys.modules[module_name]
resources.extend(getattr(module, resource_name, []))

return resources


class ComponentMeta(abc.ABCMeta):

# pylint: disable=arguments-differ
def __new__(mcs, name, bases, attributes):
component = abc.ABCMeta.__new__(mcs, name, bases, attributes)
module = attributes['__module__'].split('.')[0]
if name == 'Component' or module == 'builtins':
# Don't do the base component
# and the components loaded dynamically by load_component
# as it doesn't have the namespace.
return component

ComponentRegistry.registry.add(module)

return component


def is_number(s):
try:
float(s)
Expand Down Expand Up @@ -53,6 +97,7 @@ def wrapper(*args, **kwargs):
return wrapper


@six.add_metaclass(ComponentMeta)
class Component(collections.MutableMapping):
class _UNDEFINED(object):
def __repr__(self):
Expand Down
3 changes: 3 additions & 0 deletions dash/development/component_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
from .base_component import generate_class
from .base_component import generate_class_file
from .base_component import ComponentRegistry


def _get_metadata(metadata_path):
Expand Down Expand Up @@ -30,6 +31,8 @@ def load_components(metadata_path,
`type`, `valid_kwargs`, and `setup`.
"""

# Register the component lib for index include.
ComponentRegistry.registry.add(namespace)
components = []

data = _get_metadata(metadata_path)
Expand Down
53 changes: 10 additions & 43 deletions dash/resources.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from copy import copy
import json
import warnings
import os

from .development.base_component import Component
from .development.base_component import ComponentRegistry


# pylint: disable=old-style-class
Expand All @@ -12,6 +11,7 @@ def __init__(self, resource_name, layout):
self._resources = []
self.resource_name = resource_name
self.layout = layout
self._resources_cache = []

def append_resource(self, resource):
self._resources.append(resource)
Expand Down Expand Up @@ -59,45 +59,18 @@ def _filter_resources(self, all_resources, dev_bundles=False):
return filtered_resources

def get_all_resources(self, dev_bundles=False):
all_resources = []
if self.config.infer_from_layout:
all_resources = (
self.get_inferred_resources() + self._resources
)
else:
all_resources = self._resources
if self._resources_cache:
return self._resources_cache

return self._filter_resources(all_resources, dev_bundles)
all_resources = ComponentRegistry.get_resources(self.resource_name)
all_resources.extend(self._resources)

def get_inferred_resources(self):
namespaces = []
resources = []
layout = self.layout
self._resources_cache = res = \
self._filter_resources(all_resources, dev_bundles)
return res

def extract_resource_from_component(component):
# pylint: disable=protected-access
if (isinstance(component, Component) and
component._namespace not in namespaces):

namespaces.append(component._namespace)

if hasattr(component, self.resource_name):

component_resources = copy(
getattr(component, self.resource_name)
)
for r in component_resources:
r['namespace'] = component._namespace
resources.extend(component_resources)

extract_resource_from_component(layout)
for t in layout.traverse():
extract_resource_from_component(t)
return resources


class Css:
# pylint: disable=old-style-class
class Css: # pylint: disable=old-style-class
def __init__(self, layout=None):
self._resources = Resources('_css_dist', layout)
self._resources.config = self.config
Expand All @@ -111,9 +84,6 @@ def append_css(self, stylesheet):
def get_all_css(self):
return self._resources.get_all_resources()

def get_inferred_css_dist(self):
return self._resources.get_inferred_resources()

# pylint: disable=old-style-class, no-init, too-few-public-methods
class config:
infer_from_layout = True
Expand All @@ -134,9 +104,6 @@ def append_script(self, script):
def get_all_scripts(self, dev_bundles=False):
return self._resources.get_all_resources(dev_bundles)

def get_inferred_scripts(self):
return self._resources.get_inferred_resources()

# pylint: disable=old-style-class, no-init, too-few-public-methods
class config:
infer_from_layout = True
Expand Down
2 changes: 1 addition & 1 deletion dash/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.28.7'
__version__ = '0.29.0'
24 changes: 24 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,27 @@ def create_layout():

self.startServer(app)
time.sleep(0.5)

def test_late_component_register(self):
app = dash.Dash()

app.layout = html.Div([
html.Button('Click me to put a dcc ', id='btn-insert'),
html.Div(id='output')
])

@app.callback(Output('output', 'children'),
[Input('btn-insert', 'n_clicks')])
def update_output(value):
if value is None:
raise PreventUpdate

return dcc.Input(id='inserted-input')

self.startServer(app)

btn = self.wait_for_element_by_css_selector('#btn-insert')
btn.click()
time.sleep(1)

self.wait_for_element_by_css_selector('#inserted-input')