Skip to content

Commit 6999c5d

Browse files
committed
feat: switch code formatting to ruff
- Switch to `argparse` and clean up imports in `commands.py`. - Remove `requires-optional.txt` and `test_requirements/*` (put dependencies in `pyproject.toml` instead). - Remove `black` and use `ruff` instead. - Add `--noformat` option to `python commands.py codegen` for experimenting. - Pass output directory around to control what is formatted and linted. - Add `python commands.py format` to only do formatting. - Add `python commands.py lint` to check code. - Reformat comments in `codegen/*.py`. - Use double-quoted strings for (most) code generation. - Incorporate pandas version pinning from #5219. Note: 1. Only the generated code is formatted and linted: - `plotly/validators/**/*.py` - `plotly/graph_objs/**/*.py` - `plotly/graph_objects/__init__.py` 2. The strings in the data used by code generation are (for example) `"'some_name'"` (i.e., have embedded single quotes). This PR does not try to fix that: instead, we rely on reformatting to turn all the single-quoted strings into double-quoted strings.
1 parent a76b656 commit 6999c5d

File tree

11 files changed

+291
-376
lines changed

11 files changed

+291
-376
lines changed

codegen/__init__.py

Lines changed: 59 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os.path as opath
44
import shutil
55
import subprocess
6+
import sys
67

78
from codegen.datatypes import build_datatype_py, write_datatype_py
89
from codegen.compatibility import (
@@ -26,10 +27,6 @@
2627
get_data_validator_instance,
2728
)
2829

29-
# Target Python version for code formatting with Black.
30-
# Must be one of the values listed in pyproject.toml.
31-
BLACK_TARGET_VERSIONS = "py38 py39 py310 py311 py312"
32-
3330

3431
# Import notes
3532
# ------------
@@ -39,15 +36,14 @@
3936
# helpers that are only needed during code generation should reside in the
4037
# codegen/ package, and helpers used both during code generation and at
4138
# runtime should reside in the _plotly_utils/ package.
42-
# ----------------------------------------------------------------------------
39+
4340
def preprocess_schema(plotly_schema):
4441
"""
4542
Central location to make changes to schema before it's seen by the
4643
PlotlyNode classes
4744
"""
4845

4946
# Update template
50-
# ---------------
5147
layout = plotly_schema["layout"]["layoutAttributes"]
5248

5349
# Create codegen-friendly template scheme
@@ -89,33 +85,41 @@ def preprocess_schema(plotly_schema):
8985
items["colorscale"] = items.pop("concentrationscales")
9086

9187

92-
def perform_codegen(reformat=True):
93-
# Set root codegen output directory
94-
# ---------------------------------
95-
# (relative to project root)
96-
abs_file_path = opath.realpath(__file__)
97-
project_root = opath.dirname(opath.dirname(abs_file_path))
98-
outdir = opath.join(project_root, "plotly")
88+
def make_paths(outdir):
89+
"""Make various paths needed for formatting and linting."""
90+
91+
validators_dir = opath.join(outdir, "validators")
92+
graph_objs_dir = opath.join(outdir, "graph_objs")
93+
graph_objects_path = opath.join(outdir, "graph_objects", "__init__.py")
94+
return validators_dir, graph_objs_dir, graph_objects_path
95+
96+
97+
def lint_code(outdir):
98+
"""Check Python code using settings in pyproject.toml."""
99+
100+
subprocess.call(["ruff", "check", *make_paths(outdir)])
101+
102+
103+
def reformat_code(outdir):
104+
"""Reformat Python code using settings in pyproject.toml."""
105+
106+
subprocess.call(["ruff", "format", *make_paths(outdir)])
107+
108+
109+
def perform_codegen(outdir, noformat=False):
110+
"""Generate code (and possibly reformat)."""
111+
112+
# Get paths
113+
validators_dir, graph_objs_dir, graph_objects_path = make_paths(outdir)
99114

100115
# Delete prior codegen output
101-
# ---------------------------
102-
validators_pkgdir = opath.join(outdir, "validators")
103-
if opath.exists(validators_pkgdir):
104-
shutil.rmtree(validators_pkgdir)
105-
106-
graph_objs_pkgdir = opath.join(outdir, "graph_objs")
107-
if opath.exists(graph_objs_pkgdir):
108-
shutil.rmtree(graph_objs_pkgdir)
109-
110-
# plotly/datatypes is not used anymore, but was at one point so we'll
111-
# still delete it if we find it in case a developer is upgrading from an
112-
# older version
113-
datatypes_pkgdir = opath.join(outdir, "datatypes")
114-
if opath.exists(datatypes_pkgdir):
115-
shutil.rmtree(datatypes_pkgdir)
116+
if opath.exists(validators_dir):
117+
shutil.rmtree(validators_dir)
118+
if opath.exists(graph_objs_dir):
119+
shutil.rmtree(graph_objs_dir)
116120

117121
# Load plotly schema
118-
# ------------------
122+
project_root = opath.dirname(outdir)
119123
plot_schema_path = opath.join(
120124
project_root, "codegen", "resources", "plot-schema.json"
121125
)
@@ -124,19 +128,17 @@ def perform_codegen(reformat=True):
124128
plotly_schema = json.load(f)
125129

126130
# Preprocess Schema
127-
# -----------------
128131
preprocess_schema(plotly_schema)
129132

130133
# Build node lists
131-
# ----------------
132-
# ### TraceNode ###
134+
# TraceNode
133135
base_traces_node = TraceNode(plotly_schema)
134136
compound_trace_nodes = PlotlyNode.get_all_compound_datatype_nodes(
135137
plotly_schema, TraceNode
136138
)
137139
all_trace_nodes = PlotlyNode.get_all_datatype_nodes(plotly_schema, TraceNode)
138140

139-
# ### LayoutNode ###
141+
# LayoutNode
140142
compound_layout_nodes = PlotlyNode.get_all_compound_datatype_nodes(
141143
plotly_schema, LayoutNode
142144
)
@@ -155,14 +157,14 @@ def perform_codegen(reformat=True):
155157
if node.is_array_element and node.has_child("xref") and node.has_child("yref")
156158
]
157159

158-
# ### FrameNode ###
160+
# FrameNode
159161
compound_frame_nodes = PlotlyNode.get_all_compound_datatype_nodes(
160162
plotly_schema, FrameNode
161163
)
162164
frame_node = compound_frame_nodes[0]
163165
all_frame_nodes = PlotlyNode.get_all_datatype_nodes(plotly_schema, FrameNode)
164166

165-
# ### All nodes ###
167+
# All nodes
166168
all_datatype_nodes = all_trace_nodes + all_layout_nodes + all_frame_nodes
167169

168170
all_compound_nodes = [
@@ -172,37 +174,34 @@ def perform_codegen(reformat=True):
172174
]
173175

174176
# Write out validators
175-
# --------------------
176-
# # ### Layout ###
177+
178+
# # Layout
177179
for node in all_layout_nodes:
178180
write_validator_py(outdir, node)
179181

180-
# ### Trace ###
182+
# Trace
181183
for node in all_trace_nodes:
182184
write_validator_py(outdir, node)
183185

184-
# ### Frames ###
186+
# Frames
185187
for node in all_frame_nodes:
186188
write_validator_py(outdir, node)
187189

188-
# ### Data (traces) validator ###
190+
# Data (traces) validator
189191
write_data_validator_py(outdir, base_traces_node)
190192

191193
# Alls
192-
# ----
193194
alls = {}
194195

195196
# Write out datatypes
196-
# -------------------
197197
for node in all_compound_nodes:
198198
write_datatype_py(outdir, node)
199199

200-
# ### Deprecated ###
200+
# Deprecated
201201
# These are deprecated legacy datatypes like graph_objs.Marker
202202
write_deprecated_datatypes(outdir)
203203

204204
# Write figure class to graph_objs
205-
# --------------------------------
206205
data_validator = get_data_validator_instance(base_traces_node)
207206
layout_validator = layout_node.get_validator_instance()
208207
frame_validator = frame_node.get_validator_instance()
@@ -218,8 +217,7 @@ def perform_codegen(reformat=True):
218217
)
219218

220219
# Write validator __init__.py files
221-
# ---------------------------------
222-
# ### Write __init__.py files for each validator package ###
220+
# Write __init__.py files for each validator package
223221
validator_rel_class_imports = {}
224222
for node in all_datatype_nodes:
225223
if node.is_mapped:
@@ -239,7 +237,6 @@ def perform_codegen(reformat=True):
239237
write_init_py(validators_pkg, path_parts, [], rel_classes)
240238

241239
# Write datatype __init__.py files
242-
# --------------------------------
243240
datatype_rel_class_imports = {}
244241
datatype_rel_module_imports = {}
245242

@@ -257,16 +254,16 @@ def perform_codegen(reformat=True):
257254
f".{node.name_undercase}"
258255
)
259256

260-
# ### Write plotly/graph_objs/graph_objs.py ###
261-
# This if for backward compatibility. It just imports everything from
257+
# Write plotly/graph_objs/graph_objs.py
258+
# This is for backward compatibility. It just imports everything from
262259
# graph_objs/__init__.py
263260
write_graph_objs_graph_objs(outdir)
264261

265-
# ### Add Figure and FigureWidget ###
262+
# Add Figure and FigureWidget
266263
root_datatype_imports = datatype_rel_class_imports[()]
267264
root_datatype_imports.append("._figure.Figure")
268265

269-
# ### Add deprecations ###
266+
# Add deprecations
270267
for dep_clas in DEPRECATED_DATATYPES:
271268
root_datatype_imports.append(f"._deprecations.{dep_clas}")
272269

@@ -302,14 +299,14 @@ def __getattr__(import_name):
302299
303300
return orig_getattr(import_name)
304301
"""
305-
# ### __all__ ###
302+
# __all__
306303
for path_parts, class_names in alls.items():
307304
if path_parts and class_names:
308305
filepath = opath.join(outdir, "graph_objs", *path_parts, "__init__.py")
309306
with open(filepath, "at") as f:
310307
f.write(f"\n__all__ = {class_names}")
311308

312-
# ### Output datatype __init__.py files ###
309+
# Output datatype __init__.py files
313310
graph_objs_pkg = opath.join(outdir, "graph_objs")
314311
for path_parts in datatype_rel_class_imports:
315312
rel_classes = sorted(datatype_rel_class_imports[path_parts])
@@ -320,7 +317,7 @@ def __getattr__(import_name):
320317
init_extra = ""
321318
write_init_py(graph_objs_pkg, path_parts, rel_modules, rel_classes, init_extra)
322319

323-
# ### Output graph_objects.py alias
320+
# Output graph_objects.py alias
324321
graph_objects_rel_classes = [
325322
"..graph_objs." + rel_path.split(".")[-1]
326323
for rel_path in datatype_rel_class_imports[()]
@@ -335,22 +332,19 @@ def __getattr__(import_name):
335332
graph_objects_rel_classes,
336333
init_extra=optional_figure_widget_import,
337334
)
338-
graph_objects_path = opath.join(outdir, "graph_objects", "__init__.py")
339335
os.makedirs(opath.join(outdir, "graph_objects"), exist_ok=True)
340336
with open(graph_objects_path, "wt") as f:
341337
f.write(graph_objects_init_source)
342338

343-
# ### Run black code formatter on output directories ###
344-
if reformat:
345-
target_version = [
346-
f"--target-version={v}" for v in BLACK_TARGET_VERSIONS.split()
347-
]
348-
subprocess.call(["black", *target_version, validators_pkgdir])
349-
subprocess.call(["black", *target_version, graph_objs_pkgdir])
350-
subprocess.call(["black", *target_version, graph_objects_path])
351-
else:
339+
# Run black code formatter on output directories
340+
if noformat:
352341
print("skipping reformatting")
342+
else:
343+
reformat_code(outdir)
353344

354345

355346
if __name__ == "__main__":
356-
perform_codegen()
347+
if len(sys.argv) != 2:
348+
print("Usage: codegen [dirname]", file=sys.stderr)
349+
sys.exit(1)
350+
perform_codegen(sys.argv[1])

codegen/compatibility.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,24 @@ def build_deprecated_datatypes_py():
5252
"""
5353

5454
# Initialize source code buffer
55-
# -----------------------------
5655
buffer = StringIO()
5756

5857
# Write warnings import
59-
# ---------------------
6058
buffer.write("import warnings\n")
6159

6260
# Write warnings filter
63-
# ---------------------
6461
# Use filter to enable DeprecationWarnings on our deprecated classes
6562
buffer.write(
6663
r"""
67-
warnings.filterwarnings('default',
68-
r'plotly\.graph_objs\.\w+ is deprecated',
64+
warnings.filterwarnings("default",
65+
r"plotly\.graph_objs\.\w+ is deprecated",
6966
DeprecationWarning)
7067
7168
7269
"""
7370
)
7471

7572
# Write deprecated class definitions
76-
# ----------------------------------
7773
for class_name, opts in DEPRECATED_DATATYPES.items():
7874
base_class_name = opts["base_type"].__name__
7975
depr_msg = build_deprecation_message(class_name, **opts)
@@ -93,7 +89,6 @@ def __init__(self, *args, **kwargs):
9389
)
9490

9591
# Return source string
96-
# --------------------
9792
return buffer.getvalue()
9893

9994

@@ -175,12 +170,10 @@ def write_deprecated_datatypes(outdir):
175170
None
176171
"""
177172
# Generate source code
178-
# --------------------
179173
datatype_source = build_deprecated_datatypes_py()
180174
filepath = opath.join(outdir, "graph_objs", "_deprecations.py")
181175

182176
# Write file
183-
# ----------
184177
write_source_py(datatype_source, filepath)
185178

186179

0 commit comments

Comments
 (0)