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

Conversation

T4rk1n
Copy link
Contributor

@T4rk1n T4rk1n commented Oct 31, 2018

Refactored the component suites to register without crawling the layout by using a ComponentRegistry metaclass.

Closes #424, Closes plotly/dash-renderer#46

@T4rk1n T4rk1n force-pushed the component-suites-registration branch from b85ec7f to 4a915a4 Compare October 31, 2018 20:44
@T4rk1n
Copy link
Contributor Author

T4rk1n commented Oct 31, 2018

@rmarren1 I don't know what the matter with the component in dash_flow_example and dash_dangerously_set_inner_html. I think they don't have a generated python Component class ?

@chriddyp
Copy link
Member

Interesting, ComponentRegistry is like a global, shared import? This is a very cool solution

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Oct 31, 2018

@chriddyp It's a metaclass, __new__ get called every time a new subclass of Component get created and add the namespace to the class instance registry so it's like a global but it's not.

@chriddyp
Copy link
Member

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
We add something like:

if (!window[component.namespace]) {
   await fetch(`/_dash-component-registry?library=${component.namespace}`).then(
      res => {
          const resources = res.scripts_and_stylesheets;
          return Promise.all(resourcesmap(lib => fetch(`/_dash-component-suites/${lib}`))
      }
   )
}

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

@chriddyp
Copy link
Member

This is also one of the gnarliest bugs that users in encounter, so 💯 💯 💯 for taking this on 🥇

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Nov 1, 2018

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.

@chriddyp
Copy link
Member

chriddyp commented Nov 1, 2018

you don't have the actual bundle name that need to be loaded and the dist that comes with the component library.

Right, we'd need a new endpoint for discovery (as above /_dash-component-registry?library=${component.namespace})

But in any case, this looks like a great solution 👍

@rmarren1
Copy link
Contributor

rmarren1 commented Nov 1, 2018

@T4rk1n Yeah those repos don't have the auto-generated classes

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Nov 1, 2018

@rmarren1 I register those namespace in the load_component, works good.

@T4rk1n T4rk1n force-pushed the component-suites-registration branch from 34a3af8 to d9cd0f8 Compare November 1, 2018 15:46
@T4rk1n T4rk1n requested a review from rmarren1 November 1, 2018 15:53
@T4rk1n
Copy link
Contributor Author

T4rk1n commented Nov 1, 2018

@plotly/dash ready for reviews.

Copy link
Contributor

@rmarren1 rmarren1 left a 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.


@classmethod
def get_resources(mcs, resource_name):
cached = mcs.__dist_cache.get(resource_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cached = mcs.__dist_cache.get(resource_name)
cached = mcs.__dist_cache.get(resource_name, {})

"""Just importing a component lib will make it be loaded on the index"""

registry = set()
__dist_cache = collections.defaultdict(dict)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__dist_cache = collections.defaultdict(dict)
__dist_cache = {}

Copy link
Contributor

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.

cached = mcs.__dist_cache.get(resource_name)
current_len = len(mcs.registry)

if cached and current_len == cached.get('len'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if cached and current_len == cached.get('len'):
if mcs.registry == cached.get('cached_module_names', set()):

@classmethod
def get_resources(mcs, resource_name):
cached = mcs.__dist_cache.get(resource_name)
current_len = len(mcs.registry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
current_len = len(mcs.registry)

Copy link
Contributor

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.

return cached.get('resources')

mcs.__dist_cache[resource_name]['resources'] = resources = []
mcs.__dist_cache[resource_name]['len'] = current_len
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mcs.__dist_cache[resource_name]['len'] = current_len
mcs.__dist_cache[resource_name]['cached_module_names'] = mcs.registry

@@ -12,6 +11,10 @@ def __init__(self, resource_name, layout):
self._resources = []
self.resource_name = resource_name
self.layout = layout
self._cache = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self._cache = {
self._resources_cache = []

Copy link
Contributor

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?

Copy link
Contributor Author

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.

else:
all_resources = self._resources
cur_len = len(self._resources)
if self._cache['resources'] and cur_len == self._cache['len']:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self._cache['resources'] and cur_len == self._cache['len']:
if len(self._resources) == len(self._resources_cache):

all_resources = self._resources
cur_len = len(self._resources)
if self._cache['resources'] and cur_len == self._cache['len']:
return self._cache['resources']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return self._cache['resources']
return self._resources_cache

layout = self.layout
self._cache['resources'] = res = \
self._filter_resources(all_resources, dev_bundles)
self._cache['len'] = cur_len
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self._cache['len'] = cur_len

namespaces = []
resources = []
layout = self.layout
self._cache['resources'] = res = \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self._cache['resources'] = res = \
self._resources_cache = res = \

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Nov 3, 2018

@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.

"The class of Component is ComponentRegistry"

A metaclass is the type of a class.

>>> type(Component)
<class 'abc.ABCMeta'>

Also I'm not sure if the name ComponentRegistry makes sense, maybe ComponentMeta

I called it ComponentMeta first, then renamed to ComponentRegistry to reflect it's purpose. I'll go further and keep the ComponentRegistry class and
refactor the metaclass part to ComponentMeta again as it should be the type of the Component class.

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Nov 5, 2018

@rmarren1 I have incorporated the changes and removed the len cache busting, will integrate with hot-reload when the time come. Please review.

@chriddyp
Copy link
Member

chriddyp commented Nov 5, 2018

Copy link
Contributor

@rmarren1 rmarren1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💃 Great!

@T4rk1n T4rk1n force-pushed the component-suites-registration branch from 49e6a96 to 2449ee9 Compare November 6, 2018 16:05
@T4rk1n T4rk1n merged commit 1663fea into master Nov 6, 2018
@T4rk1n T4rk1n deleted the component-suites-registration branch November 6, 2018 16:14
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
Issue 435 - Table selected cell
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
Issue 435 - Table selected cell
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants