Skip to content

Commit f099c3f

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 some comments in `codegen/*.py` - use double-quoted strings for (most) code generation Note: 1. The strings in the data used by code generation are (for example) `"'some_name'"` (i.e., have embedded single quotes). This PR does not fix that. 2. `ruff` is run on all of the `plotly` directory, not just the various sub-directories containing generated code. On the one hand, all the handwritten files in that directory should be formatted to pass `ruff`. On the other hand, reformatting ones that aren't may surprise people.
1 parent a76b656 commit f099c3f

File tree

11 files changed

+267
-361
lines changed

11 files changed

+267
-361
lines changed

codegen/__init__.py

Lines changed: 43 additions & 50 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,16 +85,22 @@ 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 lint_code():
89+
"""Check Python code using settings in pyproject.toml."""
90+
91+
subprocess.call(["ruff", "check", "."])
92+
93+
94+
def reformat_code(outdir):
95+
"""Reformat Python code using settings in pyproject.toml."""
96+
97+
subprocess.call(["ruff", "format", outdir])
98+
99+
100+
def perform_codegen(outdir, noformat=False):
101+
"""Generate code (and possibly reformat)."""
99102

100103
# Delete prior codegen output
101-
# ---------------------------
102104
validators_pkgdir = opath.join(outdir, "validators")
103105
if opath.exists(validators_pkgdir):
104106
shutil.rmtree(validators_pkgdir)
@@ -115,7 +117,7 @@ def perform_codegen(reformat=True):
115117
shutil.rmtree(datatypes_pkgdir)
116118

117119
# Load plotly schema
118-
# ------------------
120+
project_root = opath.dirname(outdir)
119121
plot_schema_path = opath.join(
120122
project_root, "codegen", "resources", "plot-schema.json"
121123
)
@@ -124,19 +126,17 @@ def perform_codegen(reformat=True):
124126
plotly_schema = json.load(f)
125127

126128
# Preprocess Schema
127-
# -----------------
128129
preprocess_schema(plotly_schema)
129130

130131
# Build node lists
131-
# ----------------
132-
# ### TraceNode ###
132+
# TraceNode
133133
base_traces_node = TraceNode(plotly_schema)
134134
compound_trace_nodes = PlotlyNode.get_all_compound_datatype_nodes(
135135
plotly_schema, TraceNode
136136
)
137137
all_trace_nodes = PlotlyNode.get_all_datatype_nodes(plotly_schema, TraceNode)
138138

139-
# ### LayoutNode ###
139+
# LayoutNode
140140
compound_layout_nodes = PlotlyNode.get_all_compound_datatype_nodes(
141141
plotly_schema, LayoutNode
142142
)
@@ -155,14 +155,14 @@ def perform_codegen(reformat=True):
155155
if node.is_array_element and node.has_child("xref") and node.has_child("yref")
156156
]
157157

158-
# ### FrameNode ###
158+
# FrameNode
159159
compound_frame_nodes = PlotlyNode.get_all_compound_datatype_nodes(
160160
plotly_schema, FrameNode
161161
)
162162
frame_node = compound_frame_nodes[0]
163163
all_frame_nodes = PlotlyNode.get_all_datatype_nodes(plotly_schema, FrameNode)
164164

165-
# ### All nodes ###
165+
# All nodes
166166
all_datatype_nodes = all_trace_nodes + all_layout_nodes + all_frame_nodes
167167

168168
all_compound_nodes = [
@@ -172,37 +172,34 @@ def perform_codegen(reformat=True):
172172
]
173173

174174
# Write out validators
175-
# --------------------
176-
# # ### Layout ###
175+
176+
# # Layout
177177
for node in all_layout_nodes:
178178
write_validator_py(outdir, node)
179179

180-
# ### Trace ###
180+
# Trace
181181
for node in all_trace_nodes:
182182
write_validator_py(outdir, node)
183183

184-
# ### Frames ###
184+
# Frames
185185
for node in all_frame_nodes:
186186
write_validator_py(outdir, node)
187187

188-
# ### Data (traces) validator ###
188+
# Data (traces) validator
189189
write_data_validator_py(outdir, base_traces_node)
190190

191191
# Alls
192-
# ----
193192
alls = {}
194193

195194
# Write out datatypes
196-
# -------------------
197195
for node in all_compound_nodes:
198196
write_datatype_py(outdir, node)
199197

200-
# ### Deprecated ###
198+
# Deprecated
201199
# These are deprecated legacy datatypes like graph_objs.Marker
202200
write_deprecated_datatypes(outdir)
203201

204202
# Write figure class to graph_objs
205-
# --------------------------------
206203
data_validator = get_data_validator_instance(base_traces_node)
207204
layout_validator = layout_node.get_validator_instance()
208205
frame_validator = frame_node.get_validator_instance()
@@ -218,8 +215,7 @@ def perform_codegen(reformat=True):
218215
)
219216

220217
# Write validator __init__.py files
221-
# ---------------------------------
222-
# ### Write __init__.py files for each validator package ###
218+
# Write __init__.py files for each validator package
223219
validator_rel_class_imports = {}
224220
for node in all_datatype_nodes:
225221
if node.is_mapped:
@@ -239,7 +235,6 @@ def perform_codegen(reformat=True):
239235
write_init_py(validators_pkg, path_parts, [], rel_classes)
240236

241237
# Write datatype __init__.py files
242-
# --------------------------------
243238
datatype_rel_class_imports = {}
244239
datatype_rel_module_imports = {}
245240

@@ -257,16 +252,16 @@ def perform_codegen(reformat=True):
257252
f".{node.name_undercase}"
258253
)
259254

260-
# ### Write plotly/graph_objs/graph_objs.py ###
261-
# This if for backward compatibility. It just imports everything from
255+
# Write plotly/graph_objs/graph_objs.py
256+
# This is for backward compatibility. It just imports everything from
262257
# graph_objs/__init__.py
263258
write_graph_objs_graph_objs(outdir)
264259

265-
# ### Add Figure and FigureWidget ###
260+
# Add Figure and FigureWidget
266261
root_datatype_imports = datatype_rel_class_imports[()]
267262
root_datatype_imports.append("._figure.Figure")
268263

269-
# ### Add deprecations ###
264+
# Add deprecations
270265
for dep_clas in DEPRECATED_DATATYPES:
271266
root_datatype_imports.append(f"._deprecations.{dep_clas}")
272267

@@ -302,14 +297,14 @@ def __getattr__(import_name):
302297
303298
return orig_getattr(import_name)
304299
"""
305-
# ### __all__ ###
300+
# __all__
306301
for path_parts, class_names in alls.items():
307302
if path_parts and class_names:
308303
filepath = opath.join(outdir, "graph_objs", *path_parts, "__init__.py")
309304
with open(filepath, "at") as f:
310305
f.write(f"\n__all__ = {class_names}")
311306

312-
# ### Output datatype __init__.py files ###
307+
# Output datatype __init__.py files
313308
graph_objs_pkg = opath.join(outdir, "graph_objs")
314309
for path_parts in datatype_rel_class_imports:
315310
rel_classes = sorted(datatype_rel_class_imports[path_parts])
@@ -320,7 +315,7 @@ def __getattr__(import_name):
320315
init_extra = ""
321316
write_init_py(graph_objs_pkg, path_parts, rel_modules, rel_classes, init_extra)
322317

323-
# ### Output graph_objects.py alias
318+
# Output graph_objects.py alias
324319
graph_objects_rel_classes = [
325320
"..graph_objs." + rel_path.split(".")[-1]
326321
for rel_path in datatype_rel_class_imports[()]
@@ -340,17 +335,15 @@ def __getattr__(import_name):
340335
with open(graph_objects_path, "wt") as f:
341336
f.write(graph_objects_init_source)
342337

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:
338+
# Run black code formatter on output directories
339+
if noformat:
352340
print("skipping reformatting")
341+
else:
342+
reformat_code(outdir)
353343

354344

355345
if __name__ == "__main__":
356-
perform_codegen()
346+
if len(sys.argv) != 2:
347+
print("Usage: codegen [dirname]", file=sys.stderr)
348+
sys.exit(1)
349+
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)