Skip to content

Commit

Permalink
feat: modify code generation to reduce bundle size
Browse files Browse the repository at this point in the history
1.  Add `bin/get_size.py` so that `python bin/get_size.py plotly build`
    reports the number of files and total size in bytes of the `plotly`
    directory (where generated code is put) and the `build` directory
    that is populated by `python setup.py build`.

1.  Modify `codegen/__init__.py` and `./setup.py` so that
    `python setup.py --reformat=false` disables reformatting.

1.  Assign an empty string to the `data_docs` field of generated
    validators.  (This has a major impact because those docs are
    duplicated many times.)

1.  Alias name of base validator during import in
    `codegen/validators.py`.

1.  Remove the long list of CSS colors from help strings for color
    properties.

1.  Replace `super(Parent, self)` with `super()` in generated code.

1.  Drop use of sys.version_info and TYPE_CHECKING.  Removed the check
    for Python < 3.7 using `sys.version_info` and as a backup checking
    `typing.TYPE_CHECKING`; this saves a little space and also cleans
    up the code.

1.  Remove mention of Chart Studio and explicit enumeration of system
    font names from plotly.js / plot-schema.json so that this text
    isn't copied dozens of times into the plotly.py bundle.

1.  Introduce `_init_provided()` for `BaseFigure` and `BasePlotlyType`
    that calls a helper function `_initialize_provided()` to replace
    repetitions of:

```
_v = arg.pop("something", None)
_v = something if something is not None else _v
if _v is not None:
    self["something"] = _v
```

Original size of plotly/**/*.py: 42283582 bytes
Current size of plotly/**/*.py:  31931739 bytes
Change: -25%
  • Loading branch information
gvwilson committed Feb 4, 2025
1 parent 5813a8a commit 3cb9303
Show file tree
Hide file tree
Showing 14,853 changed files with 253,000 additions and 493,024 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
27 changes: 4 additions & 23 deletions _plotly_utils/basevalidators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1328,25 +1328,14 @@ def numbers_allowed(self):
return self.colorscale_path is not None

def description(self):

named_clrs_str = "\n".join(
textwrap.wrap(
", ".join(self.named_colors),
width=79 - 16,
initial_indent=" " * 12,
subsequent_indent=" " * 12,
)
)

valid_color_description = """\
The '{plotly_name}' property is a color and may be specified as:
- A hex string (e.g. '#ff0000')
- An rgb/rgba string (e.g. 'rgb(255,0,0)')
- An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
- A named CSS color:
{clrs}""".format(
plotly_name=self.plotly_name, clrs=named_clrs_str
- A named CSS color""".format(
plotly_name=self.plotly_name
)

if self.colorscale_path:
Expand Down Expand Up @@ -2483,15 +2472,11 @@ def description(self):
that may be specified as:
- An instance of :class:`{module_str}.{class_str}`
- A dict of string/value properties that will be passed
to the {class_str} constructor
Supported dict properties:
{constructor_params_str}"""
to the {class_str} constructor"""
).format(
plotly_name=self.plotly_name,
class_str=self.data_class_str,
module_str=self.module_str,
constructor_params_str=self.data_docs,
)

return desc
Expand Down Expand Up @@ -2560,15 +2545,11 @@ def description(self):
{class_str} that may be specified as:
- A list or tuple of instances of {module_str}.{class_str}
- A list or tuple of dicts of string/value properties that
will be passed to the {class_str} constructor
Supported dict properties:
{constructor_params_str}"""
will be passed to the {class_str} constructor"""
).format(
plotly_name=self.plotly_name,
class_str=self.data_class_str,
module_str=self.module_str,
constructor_params_str=self.data_docs,
)

return desc
Expand Down
32 changes: 32 additions & 0 deletions bin/get_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Calculate total size and total number of files of package."""

from pathlib import Path
import sys


def main():
"""Main driver."""
assert 2 <= len(sys.argv) <= 3, "Usage: get_size.py src_dir [build_dir]"

src_files, src_bytes = get_size(sys.argv[1])
print(f"src,files,{src_files}")
print(f"src,bytes,{src_bytes}")

if len(sys.argv) == 3:
build_files, build_bytes = get_size(sys.argv[2])
print(f"build,files,{build_files}")
print(f"build,bytes,{build_bytes}")


def get_size(root_dir):
"""Count files and size in bytes."""
num_files, num_bytes = 0, 0
for f in Path(root_dir).glob("**/*.*"):
if "__pycache__" not in str(f):
num_files += 1
num_bytes += f.stat().st_size
return num_files, num_bytes


if __name__ == "__main__":
main()
55 changes: 23 additions & 32 deletions codegen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def preprocess_schema(plotly_schema):
items["colorscale"] = items.pop("concentrationscales")


def perform_codegen():
def perform_codegen(reformat=True):
# Set root codegen output directory
# ---------------------------------
# (relative to project root)
Expand Down Expand Up @@ -267,36 +267,24 @@ def perform_codegen():
root_datatype_imports.append(f"._deprecations.{dep_clas}")

optional_figure_widget_import = f"""
if sys.version_info < (3, 7) or TYPE_CHECKING:
try:
import ipywidgets as _ipywidgets
from packaging.version import Version as _Version
if _Version(_ipywidgets.__version__) >= _Version("7.0.0"):
from ..graph_objs._figurewidget import FigureWidget
else:
raise ImportError()
except Exception:
from ..missing_anywidget import FigureWidget
else:
__all__.append("FigureWidget")
orig_getattr = __getattr__
def __getattr__(import_name):
if import_name == "FigureWidget":
try:
import ipywidgets
from packaging.version import Version
if Version(ipywidgets.__version__) >= Version("7.0.0"):
from ..graph_objs._figurewidget import FigureWidget
return FigureWidget
else:
raise ImportError()
except Exception:
from ..missing_anywidget import FigureWidget
__all__.append("FigureWidget")
orig_getattr = __getattr__
def __getattr__(import_name):
if import_name == "FigureWidget":
try:
import ipywidgets
from packaging.version import Version
if Version(ipywidgets.__version__) >= Version("7.0.0"):
from ..graph_objs._figurewidget import FigureWidget
return FigureWidget
else:
raise ImportError()
except Exception:
from ..missing_anywidget import FigureWidget
return FigureWidget
return orig_getattr(import_name)
return orig_getattr(import_name)
"""
# ### __all__ ###
for path_parts, class_names in alls.items():
Expand Down Expand Up @@ -337,9 +325,12 @@ def __getattr__(import_name):
f.write(graph_objects_init_source)

# ### Run black code formatter on output directories ###
subprocess.call(["black", "--target-version=py36", validators_pkgdir])
subprocess.call(["black", "--target-version=py36", graph_objs_pkgdir])
subprocess.call(["black", "--target-version=py36", graph_objects_path])
if reformat:
subprocess.call(["black", "--target-version=py36", validators_pkgdir])
subprocess.call(["black", "--target-version=py36", graph_objs_pkgdir])
subprocess.call(["black", "--target-version=py36", graph_objects_path])
else:
print("skipping reformatting")


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion codegen/compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __init__(self, *args, **kwargs):
{depr_msg}
\"\"\"
warnings.warn(\"\"\"{depr_msg}\"\"\", DeprecationWarning)
super({class_name}, self).__init__(*args, **kwargs)\n\n\n"""
super().__init__(*args, **kwargs)\n\n\n"""
)

# Return source string
Expand Down
10 changes: 3 additions & 7 deletions codegen/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class {datatype_class}(_{node.name_base_datatype}):\n"""
import re
_subplotid_prop_re = re.compile(
'^(' + '|'.join(_subplotid_prop_names) + r')(\d+)$')
'^(' + '|'.join(_subplotid_prop_names) + r')(\\d+)$')
"""
)

Expand Down Expand Up @@ -323,8 +323,7 @@ def __init__(self"""

buffer.write(
f"""
super({datatype_class}, self).__init__('{node.name_property}')
super().__init__('{node.name_property}')
if '_parent' in kwargs:
self._parent = kwargs['_parent']
return
Expand Down Expand Up @@ -373,10 +372,7 @@ def __init__(self"""
name_prop = subtype_node.name_property
buffer.write(
f"""
_v = arg.pop('{name_prop}', None)
_v = {name_prop} if {name_prop} is not None else _v
if _v is not None:
self['{name_prop}'] = _v"""
self._init_provided('{name_prop}', arg, {name_prop})"""
)

# ### Literals ###
Expand Down
6 changes: 2 additions & 4 deletions codegen/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ def __init__(self, data=None, layout=None,
if a property in the specification of data, layout, or frames
is invalid AND skip_invalid is False
\"\"\"
super({fig_classname} ,self).__init__(data, layout,
frames, skip_invalid,
**kwargs)
super().__init__(data, layout, frames, skip_invalid, **kwargs)
"""
)

Expand All @@ -121,7 +119,7 @@ def {wrapped_name}(self, {full_params}) -> "{fig_classname}":
'''
{getattr(BaseFigure, wrapped_name).__doc__}
'''
return super({fig_classname}, self).{wrapped_name}({param_list})
return super().{wrapped_name}({param_list})
"""
)

Expand Down
Loading

0 comments on commit 3cb9303

Please sign in to comment.