-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Conversation
b85ec7f
to
4a915a4
Compare
@rmarren1 I don't know what the matter with the component in |
Interesting, |
@chriddyp It's a metaclass, |
The other way of solving this would be by requesting components on-the-fly during rendering. That is, in here: https://github.com/plotly/dash-renderer/blob/b1cfc996563bd0b57f9487b15212dd5b782e5b3a/src/TreeContainer.js#L73
which might make it easier for other dash in other languages to do this (languages that don't have the benefit of metaclasses). But for now, I'm 👍 with this solution as it's much simpler |
This is also one of the gnarliest bugs that users in encounter, so 💯 💯 💯 for taking this on 🥇 |
I first thought to fix it in the renderer but it just doesn't work like that. While you have the namespace and the component name, you don't have the actual bundle name that need to be loaded and the dist that comes with the component library. While metaclass is a python thing, the pattern can be replicated without it. You could have the registry as a class instance/static on the base component and add the namespace to it in the constructor. I fixed the problem with the dynamically loaded components by just adding the namespace to the registry in load_components so there's no metaclass magic at play here. |
Right, we'd need a new endpoint for discovery (as above But in any case, this looks like a great solution 👍 |
@T4rk1n Yeah those repos don't have the auto-generated classes |
@rmarren1 I register those namespace in the load_component, works good. |
34a3af8
to
d9cd0f8
Compare
@plotly/dash ready for reviews. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logic looks good, I made some suggestions for readability. Also I'm not sure if the name ComponentRegistry
makes sense, maybe ComponentMeta
? I think of metaclasses as the class of a class, so it sounds strange to say "The class of Component
is ComponentRegistry
", but ComponentMeta
sounds reasonable. Also, we might want to add more functionality here in the future that would make the name ComponentRegistry
stop making sense.
dash/development/base_component.py
Outdated
|
||
@classmethod | ||
def get_resources(mcs, resource_name): | ||
cached = mcs.__dist_cache.get(resource_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cached = mcs.__dist_cache.get(resource_name) | |
cached = mcs.__dist_cache.get(resource_name, {}) |
dash/development/base_component.py
Outdated
"""Just importing a component lib will make it be loaded on the index""" | ||
|
||
registry = set() | ||
__dist_cache = collections.defaultdict(dict) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__dist_cache = collections.defaultdict(dict) | |
__dist_cache = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default to {}
is only used once, I'd rather the simpler .get(..., {})
on that one statement so it is more readable.
dash/development/base_component.py
Outdated
cached = mcs.__dist_cache.get(resource_name) | ||
current_len = len(mcs.registry) | ||
|
||
if cached and current_len == cached.get('len'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if cached and current_len == cached.get('len'): | |
if mcs.registry == cached.get('cached_module_names', set()): |
dash/development/base_component.py
Outdated
@classmethod | ||
def get_resources(mcs, resource_name): | ||
cached = mcs.__dist_cache.get(resource_name) | ||
current_len = len(mcs.registry) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
current_len = len(mcs.registry) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would these following three changes work? If I'm understanding correctly, we want to only return the cached.get('resources')
if all the modules in the registry are also in the cache. If that is the case then I think this is more explicit.
dash/development/base_component.py
Outdated
return cached.get('resources') | ||
|
||
mcs.__dist_cache[resource_name]['resources'] = resources = [] | ||
mcs.__dist_cache[resource_name]['len'] = current_len |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mcs.__dist_cache[resource_name]['len'] = current_len | |
mcs.__dist_cache[resource_name]['cached_module_names'] = mcs.registry |
dash/resources.py
Outdated
@@ -12,6 +11,10 @@ def __init__(self, resource_name, layout): | |||
self._resources = [] | |||
self.resource_name = resource_name | |||
self.layout = layout | |||
self._cache = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self._cache = { | |
self._resources_cache = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there is some extra logic with the len
parameter that I am not understanding, but should this work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The len logic is for hot-reload, it will add and remove files from the resources when changes are detected in the assets folder, not sure why I added them now and if it should be more deep.
dash/resources.py
Outdated
else: | ||
all_resources = self._resources | ||
cur_len = len(self._resources) | ||
if self._cache['resources'] and cur_len == self._cache['len']: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if self._cache['resources'] and cur_len == self._cache['len']: | |
if len(self._resources) == len(self._resources_cache): |
dash/resources.py
Outdated
all_resources = self._resources | ||
cur_len = len(self._resources) | ||
if self._cache['resources'] and cur_len == self._cache['len']: | ||
return self._cache['resources'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return self._cache['resources'] | |
return self._resources_cache |
dash/resources.py
Outdated
layout = self.layout | ||
self._cache['resources'] = res = \ | ||
self._filter_resources(all_resources, dev_bundles) | ||
self._cache['len'] = cur_len |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self._cache['len'] = cur_len |
dash/resources.py
Outdated
namespaces = [] | ||
resources = [] | ||
layout = self.layout | ||
self._cache['resources'] = res = \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self._cache['resources'] = res = \ | |
self._resources_cache = res = \ |
@rmarren1 Thanks for the review. I'll incorporate some of the suggestions but I fear using the gh suggestion will make a bunch of small commits that might not fit together so I'll do them together in a real commit.
A metaclass is the type of a class. >>> type(Component)
<class 'abc.ABCMeta'>
I called it ComponentMeta first, then renamed to ComponentRegistry to reflect it's purpose. I'll go further and keep the ComponentRegistry class and |
@rmarren1 I have incorporated the changes and removed the len cache busting, will integrate with hot-reload when the time come. Please review. |
Once we merge this, we'll want to remove some of the references to this issue in our docs: https://github.com/plotly/dash-docs/search?q=https%3A%2F%2Fgithub.com%2Fplotly%2Fdash-renderer%2Fissues%2F46&unscoped_q=https%3A%2F%2Fgithub.com%2Fplotly%2Fdash-renderer%2Fissues%2F46 🥇 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💃 Great!
49e6a96
to
2449ee9
Compare
Issue 435 - Table selected cell
Issue 435 - Table selected cell
Refactored the component suites to register without crawling the layout by using a ComponentRegistry metaclass.
Closes #424, Closes plotly/dash-renderer#46