Skip to content

Commit 1663fea

Browse files
authored
Merge pull request #444 from plotly/component-suites-registration
Register component suites by importing.
2 parents 1c94fd2 + b731b6e commit 1663fea

File tree

7 files changed

+89
-45
lines changed

7 files changed

+89
-45
lines changed

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ confidence=
5757
disable=fixme,
5858
missing-docstring,
5959
invalid-name,
60-
too-many-lines
60+
too-many-lines,
61+
old-style-class
6162
# Enable the message, report, category or checker with the given id(s). You can
6263
# either give multiple identifier separated by comma (,) or put this option
6364
# multiple time (only on the command line, not in the configuration file where

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.29.0 - 2018-11-06
2+
## Added
3+
- 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)
4+
15
## 0.28.7 - 2018-11-05
26
## Fixed
37
- 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)

dash/development/base_component.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,53 @@
22
import copy
33
import os
44
import inspect
5+
import abc
6+
import sys
7+
import six
8+
59
from ._all_keywords import kwlist
610

711

12+
# pylint: disable=no-init,too-few-public-methods
13+
class ComponentRegistry:
14+
"""Holds a registry of the namespaces used by components."""
15+
16+
registry = set()
17+
__dist_cache = {}
18+
19+
@classmethod
20+
def get_resources(cls, resource_name):
21+
cached = cls.__dist_cache.get(resource_name)
22+
23+
if cached:
24+
return cached
25+
26+
cls.__dist_cache[resource_name] = resources = []
27+
28+
for module_name in cls.registry:
29+
module = sys.modules[module_name]
30+
resources.extend(getattr(module, resource_name, []))
31+
32+
return resources
33+
34+
35+
class ComponentMeta(abc.ABCMeta):
36+
37+
# pylint: disable=arguments-differ
38+
def __new__(mcs, name, bases, attributes):
39+
component = abc.ABCMeta.__new__(mcs, name, bases, attributes)
40+
module = attributes['__module__'].split('.')[0]
41+
if name == 'Component' or module == 'builtins':
42+
# Don't do the base component
43+
# and the components loaded dynamically by load_component
44+
# as it doesn't have the namespace.
45+
return component
46+
47+
ComponentRegistry.registry.add(module)
48+
49+
return component
50+
51+
852
def is_number(s):
953
try:
1054
float(s)
@@ -53,6 +97,7 @@ def wrapper(*args, **kwargs):
5397
return wrapper
5498

5599

100+
@six.add_metaclass(ComponentMeta)
56101
class Component(collections.MutableMapping):
57102
class _UNDEFINED(object):
58103
def __repr__(self):

dash/development/component_loader.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
from .base_component import generate_class
55
from .base_component import generate_class_file
6+
from .base_component import ComponentRegistry
67

78

89
def _get_metadata(metadata_path):
@@ -30,6 +31,8 @@ def load_components(metadata_path,
3031
`type`, `valid_kwargs`, and `setup`.
3132
"""
3233

34+
# Register the component lib for index include.
35+
ComponentRegistry.registry.add(namespace)
3336
components = []
3437

3538
data = _get_metadata(metadata_path)

dash/resources.py

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
from copy import copy
21
import json
32
import warnings
43
import os
54

6-
from .development.base_component import Component
5+
from .development.base_component import ComponentRegistry
76

87

98
# pylint: disable=old-style-class
@@ -12,6 +11,7 @@ def __init__(self, resource_name, layout):
1211
self._resources = []
1312
self.resource_name = resource_name
1413
self.layout = layout
14+
self._resources_cache = []
1515

1616
def append_resource(self, resource):
1717
self._resources.append(resource)
@@ -59,45 +59,18 @@ def _filter_resources(self, all_resources, dev_bundles=False):
5959
return filtered_resources
6060

6161
def get_all_resources(self, dev_bundles=False):
62-
all_resources = []
63-
if self.config.infer_from_layout:
64-
all_resources = (
65-
self.get_inferred_resources() + self._resources
66-
)
67-
else:
68-
all_resources = self._resources
62+
if self._resources_cache:
63+
return self._resources_cache
6964

70-
return self._filter_resources(all_resources, dev_bundles)
65+
all_resources = ComponentRegistry.get_resources(self.resource_name)
66+
all_resources.extend(self._resources)
7167

72-
def get_inferred_resources(self):
73-
namespaces = []
74-
resources = []
75-
layout = self.layout
68+
self._resources_cache = res = \
69+
self._filter_resources(all_resources, dev_bundles)
70+
return res
7671

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

82-
namespaces.append(component._namespace)
83-
84-
if hasattr(component, self.resource_name):
85-
86-
component_resources = copy(
87-
getattr(component, self.resource_name)
88-
)
89-
for r in component_resources:
90-
r['namespace'] = component._namespace
91-
resources.extend(component_resources)
92-
93-
extract_resource_from_component(layout)
94-
for t in layout.traverse():
95-
extract_resource_from_component(t)
96-
return resources
97-
98-
99-
class Css:
100-
# pylint: disable=old-style-class
73+
class Css: # pylint: disable=old-style-class
10174
def __init__(self, layout=None):
10275
self._resources = Resources('_css_dist', layout)
10376
self._resources.config = self.config
@@ -111,9 +84,6 @@ def append_css(self, stylesheet):
11184
def get_all_css(self):
11285
return self._resources.get_all_resources()
11386

114-
def get_inferred_css_dist(self):
115-
return self._resources.get_inferred_resources()
116-
11787
# pylint: disable=old-style-class, no-init, too-few-public-methods
11888
class config:
11989
infer_from_layout = True
@@ -134,9 +104,6 @@ def append_script(self, script):
134104
def get_all_scripts(self, dev_bundles=False):
135105
return self._resources.get_all_resources(dev_bundles)
136106

137-
def get_inferred_scripts(self):
138-
return self._resources.get_inferred_resources()
139-
140107
# pylint: disable=old-style-class, no-init, too-few-public-methods
141108
class config:
142109
infer_from_layout = True

dash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.28.7'
1+
__version__ = '0.29.0'

tests/test_integration.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,3 +531,27 @@ def create_layout():
531531

532532
self.startServer(app)
533533
time.sleep(0.5)
534+
535+
def test_late_component_register(self):
536+
app = dash.Dash()
537+
538+
app.layout = html.Div([
539+
html.Button('Click me to put a dcc ', id='btn-insert'),
540+
html.Div(id='output')
541+
])
542+
543+
@app.callback(Output('output', 'children'),
544+
[Input('btn-insert', 'n_clicks')])
545+
def update_output(value):
546+
if value is None:
547+
raise PreventUpdate
548+
549+
return dcc.Input(id='inserted-input')
550+
551+
self.startServer(app)
552+
553+
btn = self.wait_for_element_by_css_selector('#btn-insert')
554+
btn.click()
555+
time.sleep(1)
556+
557+
self.wait_for_element_by_css_selector('#inserted-input')

0 commit comments

Comments
 (0)