plot3 0.8.1__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {plot3-0.8.1 → plot3-0.9.0}/PKG-INFO +2 -2
  2. plot3-0.9.0/plot3/__version__.py +1 -0
  3. {plot3-0.8.1 → plot3-0.9.0}/plot3/jupyter.py +57 -49
  4. {plot3-0.8.1 → plot3-0.9.0}/plot3/masking.py +20 -1
  5. {plot3-0.8.1 → plot3-0.9.0}/plot3.egg-info/PKG-INFO +2 -2
  6. {plot3-0.8.1 → plot3-0.9.0}/plot3.egg-info/SOURCES.txt +1 -0
  7. plot3-0.9.0/plot3.egg-info/entry_points.txt +2 -0
  8. {plot3-0.8.1 → plot3-0.9.0}/plot3.egg-info/requires.txt +1 -1
  9. {plot3-0.8.1 → plot3-0.9.0}/pyproject.toml +6 -1
  10. {plot3-0.8.1 → plot3-0.9.0}/tests/test_masking.py +21 -11
  11. plot3-0.8.1/plot3/__version__.py +0 -1
  12. {plot3-0.8.1 → plot3-0.9.0}/LICENSE +0 -0
  13. {plot3-0.8.1 → plot3-0.9.0}/README.md +0 -0
  14. {plot3-0.8.1 → plot3-0.9.0}/plot3/__init__.py +0 -0
  15. {plot3-0.8.1 → plot3-0.9.0}/plot3/aesexpr.py +0 -0
  16. {plot3-0.8.1 → plot3-0.9.0}/plot3/build.py +0 -0
  17. {plot3-0.8.1 → plot3-0.9.0}/plot3/calculus.py +0 -0
  18. {plot3-0.8.1 → plot3-0.9.0}/plot3/compose.py +0 -0
  19. {plot3-0.8.1 → plot3-0.9.0}/plot3/contour.py +0 -0
  20. {plot3-0.8.1 → plot3-0.9.0}/plot3/craft.py +0 -0
  21. {plot3-0.8.1 → plot3-0.9.0}/plot3/encode.py +0 -0
  22. {plot3-0.8.1 → plot3-0.9.0}/plot3/expr.py +0 -0
  23. {plot3-0.8.1 → plot3-0.9.0}/plot3/flip.py +0 -0
  24. {plot3-0.8.1 → plot3-0.9.0}/plot3/function.py +0 -0
  25. {plot3-0.8.1 → plot3-0.9.0}/plot3/geoms.py +0 -0
  26. {plot3-0.8.1 → plot3-0.9.0}/plot3/ggplot.py +0 -0
  27. {plot3-0.8.1 → plot3-0.9.0}/plot3/io.py +0 -0
  28. {plot3-0.8.1 → plot3-0.9.0}/plot3/kde.py +0 -0
  29. {plot3-0.8.1 → plot3-0.9.0}/plot3/latexin.py +0 -0
  30. {plot3-0.8.1 → plot3-0.9.0}/plot3/mathtext.py +0 -0
  31. {plot3-0.8.1 → plot3-0.9.0}/plot3/payload.py +0 -0
  32. {plot3-0.8.1 → plot3-0.9.0}/plot3/remote.py +0 -0
  33. {plot3-0.8.1 → plot3-0.9.0}/plot3/scales.py +0 -0
  34. {plot3-0.8.1 → plot3-0.9.0}/plot3/scaling.py +0 -0
  35. {plot3-0.8.1 → plot3-0.9.0}/plot3/special.py +0 -0
  36. {plot3-0.8.1 → plot3-0.9.0}/plot3/stat2d.py +0 -0
  37. {plot3-0.8.1 → plot3-0.9.0}/plot3/static.py +0 -0
  38. {plot3-0.8.1 → plot3-0.9.0}/plot3/stats3d.py +0 -0
  39. {plot3-0.8.1 → plot3-0.9.0}/plot3/table.py +0 -0
  40. {plot3-0.8.1 → plot3-0.9.0}/plot3/themes.py +0 -0
  41. {plot3-0.8.1 → plot3-0.9.0}/plot3/viewer.py +0 -0
  42. {plot3-0.8.1 → plot3-0.9.0}/plot3.egg-info/dependency_links.txt +0 -0
  43. {plot3-0.8.1 → plot3-0.9.0}/plot3.egg-info/top_level.txt +0 -0
  44. {plot3-0.8.1 → plot3-0.9.0}/setup.cfg +0 -0
  45. {plot3-0.8.1 → plot3-0.9.0}/tests/test_2d_density.py +0 -0
  46. {plot3-0.8.1 → plot3-0.9.0}/tests/test_3d.py +0 -0
  47. {plot3-0.8.1 → plot3-0.9.0}/tests/test_animation.py +0 -0
  48. {plot3-0.8.1 → plot3-0.9.0}/tests/test_annotate.py +0 -0
  49. {plot3-0.8.1 → plot3-0.9.0}/tests/test_api_public.py +0 -0
  50. {plot3-0.8.1 → plot3-0.9.0}/tests/test_array_backend.py +0 -0
  51. {plot3-0.8.1 → plot3-0.9.0}/tests/test_arrow.py +0 -0
  52. {plot3-0.8.1 → plot3-0.9.0}/tests/test_build_contract.py +0 -0
  53. {plot3-0.8.1 → plot3-0.9.0}/tests/test_contour.py +0 -0
  54. {plot3-0.8.1 → plot3-0.9.0}/tests/test_everyday_ggplot2.py +0 -0
  55. {plot3-0.8.1 → plot3-0.9.0}/tests/test_expr.py +0 -0
  56. {plot3-0.8.1 → plot3-0.9.0}/tests/test_facets_compose.py +0 -0
  57. {plot3-0.8.1 → plot3-0.9.0}/tests/test_fast_paths.py +0 -0
  58. {plot3-0.8.1 → plot3-0.9.0}/tests/test_geom_function.py +0 -0
  59. {plot3-0.8.1 → plot3-0.9.0}/tests/test_geoms_ds.py +0 -0
  60. {plot3-0.8.1 → plot3-0.9.0}/tests/test_ggplot2_extras2.py +0 -0
  61. {plot3-0.8.1 → plot3-0.9.0}/tests/test_ggplot2_parity.py +0 -0
  62. {plot3-0.8.1 → plot3-0.9.0}/tests/test_ggplot_extras.py +0 -0
  63. {plot3-0.8.1 → plot3-0.9.0}/tests/test_grammar.py +0 -0
  64. {plot3-0.8.1 → plot3-0.9.0}/tests/test_latex_input.py +0 -0
  65. {plot3-0.8.1 → plot3-0.9.0}/tests/test_lidar_scene.py +0 -0
  66. {plot3-0.8.1 → plot3-0.9.0}/tests/test_math_layers.py +0 -0
  67. {plot3-0.8.1 → plot3-0.9.0}/tests/test_mathtext.py +0 -0
  68. {plot3-0.8.1 → plot3-0.9.0}/tests/test_payload.py +0 -0
  69. {plot3-0.8.1 → plot3-0.9.0}/tests/test_payload_display.py +0 -0
  70. {plot3-0.8.1 → plot3-0.9.0}/tests/test_point_cloud.py +0 -0
  71. {plot3-0.8.1 → plot3-0.9.0}/tests/test_r_oracle_parity.py +0 -0
  72. {plot3-0.8.1 → plot3-0.9.0}/tests/test_readme.py +0 -0
  73. {plot3-0.8.1 → plot3-0.9.0}/tests/test_remote_bridge.py +0 -0
  74. {plot3-0.8.1 → plot3-0.9.0}/tests/test_review_fixes.py +0 -0
  75. {plot3-0.8.1 → plot3-0.9.0}/tests/test_save_menu.py +0 -0
  76. {plot3-0.8.1 → plot3-0.9.0}/tests/test_scales_api.py +0 -0
  77. {plot3-0.8.1 → plot3-0.9.0}/tests/test_scales_themes.py +0 -0
  78. {plot3-0.8.1 → plot3-0.9.0}/tests/test_special.py +0 -0
  79. {plot3-0.8.1 → plot3-0.9.0}/tests/test_stat2d.py +0 -0
  80. {plot3-0.8.1 → plot3-0.9.0}/tests/test_static_save.py +0 -0
  81. {plot3-0.8.1 → plot3-0.9.0}/tests/test_stats_semantic.py +0 -0
  82. {plot3-0.8.1 → plot3-0.9.0}/tests/test_step3_geoms.py +0 -0
  83. {plot3-0.8.1 → plot3-0.9.0}/tests/test_table_backends.py +0 -0
  84. {plot3-0.8.1 → plot3-0.9.0}/tests/test_titles_theme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3
3
- Version: 0.8.1
3
+ Version: 0.9.0
4
4
  Summary: ggplot2 grammar of graphics for Python: interactive WebGL figures and journal-ready PNG, SVG, and PDF
5
5
  Author: Rigoberto Leyva Salmeron
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: numpy>=1.24
19
19
  Requires-Dist: pandas>=2.0
20
- Requires-Dist: tidy3syntax<0.2,>=0.1
20
+ Requires-Dist: tidy3syntax<0.2,>=0.1.2
21
21
  Provides-Extra: pdf
22
22
  Requires-Dist: cairosvg>=2.7; extra == "pdf"
23
23
  Provides-Extra: dev
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -4,10 +4,12 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import shlex
7
+ import dataclasses
7
8
  from typing import Any
8
9
 
9
10
  import numpy as np
10
11
  import pandas as pd
12
+ import tidy3syntax.ipython
11
13
 
12
14
  try:
13
15
  from IPython import get_ipython
@@ -17,13 +19,11 @@ except Exception: # pragma: no cover
17
19
  from plot3.io import ssh_bytes
18
20
  from plot3.masking import (
19
21
  BT_NAME,
22
+ DIALECT,
20
23
  Plot3MaskTransformer,
24
+ _plot3_names,
21
25
  is_backtick_transformer,
22
26
  is_mask_transformer,
23
- is_tidy3_backtick_transformer,
24
- plot3_backtick_transform,
25
- plot3_formula_transform,
26
- plot3_layer_transform,
27
27
  )
28
28
 
29
29
  # R-style bare-name / backtick masking for aes() / facet_wrap().
@@ -60,34 +60,39 @@ def enable_r_style(ipython: Any | None = None) -> bool:
60
60
  if ns is not None:
61
61
  # Escape hatch if AST missed a sentinel (still a valid column string).
62
62
  ns[BT_NAME] = _bt_fallback
63
- # When tidy3 is not loaded, leave tidy3 sentinel unset; plot3 AST
64
- # still rewrites __tidy3_bt__ if tidy3's preparser ran first.
65
63
 
66
- # Source preparser: when tidy3 already rewrites backticks, run only
67
- # plot3's own formula rewrites (^ in geom_function, ~ in facets), after it.
64
+ # The source rewrites (backticks, facet formulas, ^ in geom_function,
65
+ # + layer lines) go through tidy3syntax's coordinator, one transformer
66
+ # shared with tidy3, tidy3a and stats3; the AST pass masks aes() names.
67
+ _remove_own(ipython)
68
+ tidy3syntax.ipython.register(ipython, _dialect(), ast_transformer=Plot3MaskTransformer())
69
+ return True
70
+
71
+
72
+ def _dialect():
73
+ """plot3's dialect, with every public plot3 name as a layer a "+" line may add."""
74
+ return dataclasses.replace(DIALECT, layer_calls=frozenset(_plot3_names()))
75
+
76
+
77
+ def _layers_only():
78
+ """Only the "+" layer lines, for R-style masking turned off."""
79
+ return dataclasses.replace(
80
+ _dialect(), formula_calls=(), formula_keywords=(), power_calls=(), unary_formula_calls=()
81
+ )
82
+
83
+
84
+ def _remove_own(ipython: Any) -> None:
85
+ """An earlier plot3's own transformers, from before the coordinator."""
68
86
  for attr in ("input_transformers_cleanup", "input_transformers_post"):
69
87
  transformers = getattr(ipython, attr, None)
70
- if not isinstance(transformers, list):
71
- continue
72
- transformers[:] = [
73
- t for t in transformers if not is_backtick_transformer(t)
74
- ]
75
- tidy3_at = [
76
- i for i, t in enumerate(transformers) if is_tidy3_backtick_transformer(t)
77
- ]
78
- if tidy3_at:
79
- transformers.insert(tidy3_at[-1] + 1, plot3_formula_transform)
80
- else:
81
- transformers.insert(0, plot3_backtick_transform)
82
-
83
- # AST pass for bare names inside aes / facet_wrap.
88
+ if isinstance(transformers, list):
89
+ transformers[:] = [
90
+ t for t in transformers
91
+ if not is_backtick_transformer(t) and getattr(t, "__name__", "") != "plot3_layer_transform"
92
+ ]
84
93
  ast_transformers = getattr(ipython, "ast_transformers", None)
85
94
  if isinstance(ast_transformers, list):
86
- ast_transformers[:] = [
87
- t for t in ast_transformers if not is_mask_transformer(t)
88
- ]
89
- ast_transformers.append(Plot3MaskTransformer())
90
- return True
95
+ ast_transformers[:] = [t for t in ast_transformers if not is_mask_transformer(t)]
91
96
 
92
97
 
93
98
  def disable_r_style(ipython: Any | None = None) -> None:
@@ -101,17 +106,9 @@ def disable_r_style(ipython: Any | None = None) -> None:
101
106
  ipython = None
102
107
  if ipython is None:
103
108
  return
104
- for attr in ("input_transformers_cleanup", "input_transformers_post"):
105
- transformers = getattr(ipython, attr, None)
106
- if isinstance(transformers, list):
107
- transformers[:] = [
108
- t for t in transformers if not is_backtick_transformer(t)
109
- ]
110
- ast_transformers = getattr(ipython, "ast_transformers", None)
111
- if isinstance(ast_transformers, list):
112
- ast_transformers[:] = [
113
- t for t in ast_transformers if not is_mask_transformer(t)
114
- ]
109
+ _remove_own(ipython)
110
+ # The "+" layer lines stay, as before: only the R-style masking goes.
111
+ tidy3syntax.ipython.register(ipython, _layers_only())
115
112
 
116
113
 
117
114
  def r_style_enabled() -> bool:
@@ -482,20 +479,20 @@ def enable_layer_lines(ipython: Any | None = None) -> bool:
482
479
  ipython = get_ipython() if get_ipython is not None else None
483
480
  except Exception:
484
481
  ipython = None
485
- transformers = getattr(ipython, "input_transformers_cleanup", None)
486
- if not isinstance(transformers, list):
482
+ if ipython is None or not isinstance(getattr(ipython, "input_transformers_cleanup", None), list):
487
483
  return False
488
- transformers[:] = [
489
- t for t in transformers if getattr(t, "__name__", "") != "plot3_layer_transform"
490
- ]
491
- transformers.append(plot3_layer_transform)
484
+ # The layer lines are part of plot3's dialect in tidy3syntax's coordinator.
485
+ if "plot3" not in tidy3syntax.ipython.registered(ipython):
486
+ tidy3syntax.ipython.register(ipython, _layers_only())
492
487
  return True
493
488
 
494
489
 
495
- def register_plot3(*, quiet=True, r_style: bool = True) -> bool:
496
- if get_ipython is None:
497
- return False
498
- ip = get_ipython()
490
+ def register_plot3(*, quiet=True, r_style: bool = True, ipython: Any | None = None) -> bool:
491
+ ip = ipython
492
+ if ip is None:
493
+ if get_ipython is None:
494
+ return False
495
+ ip = get_ipython()
499
496
  if ip is None:
500
497
  return False
501
498
  ok = False
@@ -546,4 +543,15 @@ def register_plot3(*, quiet=True, r_style: bool = True) -> bool:
546
543
 
547
544
 
548
545
  def load_ipython_extension(ip=None):
549
- register_plot3(quiet=True)
546
+ register_plot3(quiet=True, ipython=ip)
547
+
548
+
549
+ def dialect():
550
+ """plot3's dialect for tidy3syntax (its ``tidy3syntax.dialects`` entry point): the
551
+ notebook loader, and plot3's bare-name masking for exports."""
552
+ from plot3.masking import apply_masking, default_known_names
553
+
554
+ def mask(source: str, known: frozenset[str]) -> str:
555
+ return apply_masking(source, known=set(known) | default_known_names(), backticks=False)
556
+
557
+ return dataclasses.replace(_dialect(), extension="plot3.jupyter:load_ipython_extension", mask=mask)
@@ -43,7 +43,8 @@ from tidy3syntax.scanner import Spans, apply
43
43
  # Sentinels (plot3 own + tidy3 compatibility).
44
44
  BT_NAME = "__plot3_bt__"
45
45
  TIDY3_BT_NAME = "__tidy3_bt__"
46
- _BT_SENTINELS = frozenset({BT_NAME, TIDY3_BT_NAME})
46
+ # tidy3syntax's neutral name too: another package's pass (tidy3a's) may rewrite a backtick in aes() first.
47
+ _BT_SENTINELS = frozenset({BT_NAME, TIDY3_BT_NAME, tidy3syntax.NAME})
47
48
 
48
49
  # Calls whose arguments / keywords are column *selectors* (→ strings).
49
50
  _SELECTOR_FUNCS = frozenset(
@@ -493,6 +494,22 @@ class Plot3MaskTransformer(ast.NodeTransformer):
493
494
 
494
495
  def __init__(self, known: set[str] | None = None):
495
496
  self._known_static = known
497
+ self._owners: tidy3syntax.Owners | None = None
498
+
499
+ def visit_Module(self, node: ast.Module) -> ast.AST:
500
+ # Whose aes() or facet_wrap() each call is: a notebook's own function of that name keeps
501
+ # its arguments as written (tidy3syntax's rule, as for the source rewrites).
502
+ namespace = None if self._known_static is not None else _session_namespace(_SESSION)
503
+ self._owners = tidy3syntax.Owners(node, namespace, [DIALECT])
504
+ try:
505
+ return self.generic_visit(node)
506
+ finally:
507
+ self._owners = None
508
+
509
+ def _plot3s(self, func: ast.AST) -> bool:
510
+ if self._owners is None:
511
+ return True
512
+ return self._owners.owner(func, (func.lineno, func.col_offset)) == "plot3"
496
513
 
497
514
  def _known(self) -> set[str]:
498
515
  if self._known_static is not None:
@@ -541,6 +558,8 @@ class Plot3MaskTransformer(ast.NodeTransformer):
541
558
  return self.generic_visit(node)
542
559
 
543
560
  name = node.func.id
561
+ if (name in _FORMULA_CALLS or name in _SELECTOR_FUNCS) and not self._plot3s(node.func):
562
+ return self.generic_visit(node)
544
563
  if name in _FORMULA_CALLS:
545
564
  return self._visit_geom_function(node)
546
565
  if name not in _SELECTOR_FUNCS:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3
3
- Version: 0.8.1
3
+ Version: 0.9.0
4
4
  Summary: ggplot2 grammar of graphics for Python: interactive WebGL figures and journal-ready PNG, SVG, and PDF
5
5
  Author: Rigoberto Leyva Salmeron
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: numpy>=1.24
19
19
  Requires-Dist: pandas>=2.0
20
- Requires-Dist: tidy3syntax<0.2,>=0.1
20
+ Requires-Dist: tidy3syntax<0.2,>=0.1.2
21
21
  Provides-Extra: pdf
22
22
  Requires-Dist: cairosvg>=2.7; extra == "pdf"
23
23
  Provides-Extra: dev
@@ -35,6 +35,7 @@ plot3/viewer.py
35
35
  plot3.egg-info/PKG-INFO
36
36
  plot3.egg-info/SOURCES.txt
37
37
  plot3.egg-info/dependency_links.txt
38
+ plot3.egg-info/entry_points.txt
38
39
  plot3.egg-info/requires.txt
39
40
  plot3.egg-info/top_level.txt
40
41
  tests/test_2d_density.py
@@ -0,0 +1,2 @@
1
+ [tidy3syntax.dialects]
2
+ plot3 = plot3.jupyter:dialect
@@ -1,6 +1,6 @@
1
1
  numpy>=1.24
2
2
  pandas>=2.0
3
- tidy3syntax<0.2,>=0.1
3
+ tidy3syntax<0.2,>=0.1.2
4
4
 
5
5
  [dev]
6
6
  pytest>=7.0
@@ -24,13 +24,18 @@ dependencies = [
24
24
  "pandas>=2.0",
25
25
  # The notebook syntax tidy3, tidy3a, plot3 and stats3 share (facet
26
26
  # formulas, ^ in geom_function, backtick names). Standard library only.
27
- "tidy3syntax>=0.1,<0.2",
27
+ "tidy3syntax>=0.1.2,<0.2",
28
28
  ]
29
29
 
30
30
  [project.optional-dependencies]
31
31
  pdf = ["cairosvg>=2.7"]
32
32
  dev = ["pytest>=7.0", "polars>=1.0", "contourpy>=1.0"]
33
33
 
34
+ # A notebook cell that imports plot3 gets its syntax in that same cell
35
+ # (its dialect names the notebook loader tidy3syntax's coordinator calls).
36
+ [project.entry-points."tidy3syntax.dialects"]
37
+ plot3 = "plot3.jupyter:dialect"
38
+
34
39
  [project.urls]
35
40
  Homepage = "https://github.com/rleyvasal/plot3"
36
41
  Changelog = "https://github.com/rleyvasal/plot3/blob/main/CHANGELOG.md"
@@ -309,11 +309,10 @@ _fake_tidy3_transform.__module__ = "tidy3.masking"
309
309
  _fake_tidy3_transform.__name__ = "tidy3_backtick_transform"
310
310
 
311
311
 
312
- def test_formula_rewrites_run_after_tidy3_preparser():
313
- # tidy3 imported first: plot3 still rewrites ^ in geom_function and ~ in
314
- # facets, after tidy3's backticks, and installs no second backtick pass.
312
+ def test_one_coordinator_ahead_of_other_transformers():
313
+ # plot3's source rewrites run in tidy3syntax's coordinator, one transformer
314
+ # for every package, put first; other packages' transformers stay.
315
315
  from plot3.jupyter import enable_r_style
316
- from plot3.masking import plot3_formula_transform
317
316
 
318
317
  def other(lines):
319
318
  return lines
@@ -321,15 +320,21 @@ def test_formula_rewrites_run_after_tidy3_preparser():
321
320
  ip = _shell(_fake_tidy3_transform, other)
322
321
  enable_r_style(ip)
323
322
  enable_r_style(ip) # idempotent
324
- assert ip.input_transformers_cleanup == [
325
- _fake_tidy3_transform, plot3_formula_transform, other,
326
- ]
327
- out = "".join(plot3_formula_transform(["p = geom_function(y = x^3) + facet_wrap(~cyl)\n"]))
323
+ coordinator, *rest = ip.input_transformers_cleanup
324
+ assert getattr(coordinator, "tidy3syntax", False)
325
+ assert rest == [_fake_tidy3_transform, other]
326
+ out = "".join(coordinator(["p = geom_function(y = x^3) + facet_wrap(~cyl)\n"]))
328
327
  assert out == "p = geom_function(y = x**3) + facet_wrap('~cyl')\n"
328
+ assert out == "".join(coordinator([out])) # a second pass changes nothing
329
329
 
330
- alone = _shell(other)
331
- enable_r_style(alone)
332
- assert alone.input_transformers_cleanup == [plot3_backtick_transform, other]
330
+
331
+ def test_a_notebooks_own_aes_keeps_its_arguments():
332
+ from plot3.masking import Plot3MaskTransformer
333
+
334
+ tree = ast.parse("def aes(**kw):\n return kw\nr = aes(x=wt)")
335
+ assert "x=wt" in ast.unparse(Plot3MaskTransformer(known=default_known_names()).visit(tree))
336
+ tree = ast.parse("r = aes(x=wt)")
337
+ assert "x='wt'" in ast.unparse(Plot3MaskTransformer(known=default_known_names()).visit(tree))
333
338
 
334
339
 
335
340
  # ── Valid Python keeps its meaning outside plot3's calls ─────────────────────
@@ -435,3 +440,8 @@ def test_plot3_calls_still_take_r_syntax_in_a_notebook():
435
440
  from IPython.core.interactiveshell import InteractiveShell
436
441
 
437
442
  InteractiveShell.clear_instance()
443
+
444
+
445
+ def test_the_shared_backtick_placeholder_is_a_column_name():
446
+ # tidy3a's notebook pass may rewrite `First Name` in aes() before plot3's runs.
447
+ assert _norm("aes(x=__r_name__('First Name'), y=mpg)") == "aes(x='First Name', y='mpg')"
@@ -1 +0,0 @@
1
- __version__ = "0.8.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes