-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Combine extract-meta and component generation in a cli. #451
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
Conversation
This seems like a good step forward. A few questions:
|
Yes, the old code was not modified.
Coming up soon.
Just need to add |
|
||
|
||
# pylint: disable=too-many-locals | ||
def generate_components(component_src, output_dir): |
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 would rename output_dir
to project_shortname
and not treat it as a path.
cmd = shlex.split('node {} {}'.format(extract_path, component_src), | ||
posix=not is_windows) | ||
|
||
namespace = os.path.basename(output_dir) |
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 would just remove namespace
as a variable here.
) | ||
sys.exit(1) | ||
# pylint: disable=unbalanced-tuple-unpacking | ||
src, out = sys.argv[1:] |
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.
again, I would rename this project_shortname
instead of out
... because the R code won't output there.
component_data['description'], | ||
namespace | ||
) | ||
print('Generated {}/{}.py'.format(namespace, 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.
for the Python piece, if it turns out that we do need package.json
in the Python module directory next to __init__.py
then I would do the copying here
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.
See also plotly/dash-component-boilerplate#38
💃 once the tests pass and the internal logic of the program are little bit more language-agnostic :) |
04f7761
to
82e22c3
Compare
component_data['description'], | ||
project_shortname | ||
) | ||
print('Generated {}/{}.py'.format(project_shortname, 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.
can we move this line into the generate_class_file
function? It's Python-specific and there will likely be an R-specific version as well and i don't think it's good to duplicate the path-as-function-of-project_shortname logic here as well :)
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 don't think it's good to duplicate the path-as-function-of-project_shortname logic here as well
I don't understand what you mean by that, just print the component 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.
I'm saying that in the case of Python, the file path happens to be project_shortname/name`` but for R it'll be
camelCase(project_shortname)/name.R` and I don't want to duplicate that logic here...
We could just print Generated {lang} version of {component_name}
instead and bypass the language-specific path details :)
components = [] | ||
|
||
for component_path, component_data in metadata.items(): | ||
name = component_path.split('/')[-1].split('.')[0] |
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.
maybe name this component_name
just for extra clarity/readability
with open(os.path.join(project_shortname, 'metadata.json'), 'w') as f: | ||
json.dump(metadata, f) | ||
|
||
with open(os.path.join(project_shortname, '_imports_.py'), 'w') as f: |
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.
can we move this Python-specific stuff into a Python-specific place? doesn't have to be in this PR but just in general :)
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.
It's actually duplicate code so let's refactor that for the old method too.
|
||
from dash.development.component_loader import generate_imports |
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.
should this also be a relative import? :)
|
||
components = [] | ||
|
||
for component_path, component_data in metadata.items(): |
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.
as a general comment, this seems to be repeated code with
dash/dash/development/component_loader.py
Line 68 in d892b9e
name = componentPath.split('/').pop().split('.')[0] |
@nicolaskruchten Tests are in plotly/dash-component-boilerplate#40 @rmarren1 Please review |
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.
This is great! I think we should fully commit to one method of generating class files.
With this change it looks like you can either
- Run the
extract-meta.js
command manually to getmetadata.json
- Run
dash.development.component_loader.generate_classes
pointing to the newmetadata.json
OR - Run
dash-generate-components
to do all that in one function.
So we can either
- In
generate_components
, we can writemetadata.json
first, then use a call todash.development.component_loader.generate_classes
from withingenerate_components
to generate the components. - Just delete the
dash.development.component_loader.generate_classes
function and rely on the CLI.
I think 2 might cause some minor headaches since we would need to update the toolchains in dash-*-components
to use the CLI rather than dash.development.component_loader.generate_classes
, but also dash.development.component_loader.generate_classes
isn't really doing much. I'm pro deleting un-needed code, so I would go with #2 unless there is something I am not thinking of.
Also, I think base_component
is getting a bit busy, I would be in favor of moving all the I/O functions to component_generator.py
, e.g. generate_class_file
, generate_imports
, generate_classes_files
.
Other than the method to get the path to extract-meta.js
is the only critical change here.
dash/development/base_component.py
Outdated
@@ -4,6 +4,8 @@ | |||
import inspect | |||
import abc | |||
import sys | |||
import textwrap |
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.
🐱
dash/development/base_component.py
Outdated
@@ -466,6 +468,8 @@ def generate_class_file(typename, props, description, namespace): | |||
f.write(import_string) | |||
f.write(class_string) | |||
|
|||
print('Generated {}'.format(file_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.
🐱
dash/development/base_component.py
Outdated
).lstrip()) | ||
|
||
|
||
def generate_classes_files(project_shortname, metadata, *component_generators): |
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.
Can this be generate_class_files
to match generate_class
and generate_class_file
method names?
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.
Also, can it be moved up to be next to those methods? Just to keep them together spatially in the code
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.
Actually, perhaps we can just move these into components_generator.py
package_info_filename='package.json'): | ||
is_windows = sys.platform == 'win32' | ||
|
||
extract_path = os.path.abspath(os.path.join( |
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.
This pattern for accessing the included files has given me problems in the past, from what I researched to fix it we should use https://stackoverflow.com/a/20885799
)) | ||
|
||
os.environ['NODE_PATH'] = 'node_modules' | ||
cmd = shlex.split('node {} {}'.format(extract_path, components_source), |
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.
Is this different from ['node', extract_path, component_source]
? Never used shlex
not sure what this does.
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 use shlex
to make sure it works across platforms, had problems on windows passing arguments without the posix=False
argument.
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.
Sounds good 👍
# Add an import statement for this component | ||
with open(imports_path, 'a') as f: | ||
f.write('from .{0:s} import {0:s}\n'.format(name)) | ||
components = generate_classes_files(namespace, data, generate_class_file) |
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.
Four lines up is not relevant anymore, write append not used.
@rmarren1 I think we need to keep supporting both methods for backwards-compatibility, otherwise older components may break, no? |
Writing just to read back seems like a bad pattern, I initially did not write the
I think they should stay there for backward compatibility. Someone working on a component library should be able to update dash without breaking their build system. |
I moved all python class generation code to |
💃 Deleting the non-cli method of generating component class files would only break build systems for component authors and shouldn't affect things for end users (as long as we keep the |
@rmarren1 FWIW here's the terminology I use when thinking about our stakeholders:
In general our commitment should be not to break things for any of these folks :) |
@T4rk1n I'm a bit confused about what the |
@nicolaskruchten The component_loader is the old method for generating the components, it contains the methods for generating components at runtime and the method we were using in |
Some history: In the first dash iteration, component libraries would call #276 changed this, so that that component classes were generated once at build time using Now this CLI will replace the |
thanks for the clarifications! 💃 on my end |
extract-meta.js
extract-meta.js
call and component generation.dash-generate-components
Usage:
$ dash-generate-components src/lib/components my_components_namespace
cc @plotly/dash
Closes plotly/dash-component-boilerplate#17