plotly-print 0.2.2__tar.gz → 0.2.3__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 (83) hide show
  1. plotly_print-0.2.3/.cursor/rules/global.mdc +52 -0
  2. {plotly_print-0.2.2 → plotly_print-0.2.3}/.cursor/rules/javascript.mdc +2 -1
  3. plotly_print-0.2.3/.cursor/rules/map.mdc +34 -0
  4. plotly_print-0.2.3/.cursorignore +2 -0
  5. plotly_print-0.2.3/AGENTS.md +17 -0
  6. {plotly_print-0.2.2 → plotly_print-0.2.3}/PKG-INFO +1 -1
  7. {plotly_print-0.2.2 → plotly_print-0.2.3}/pyproject.toml +1 -1
  8. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/engine.py +66 -2
  9. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_json_types.py +58 -0
  10. {plotly_print-0.2.2 → plotly_print-0.2.3}/uv.lock +1 -1
  11. plotly_print-0.2.2/.cursor/rules/artifacts.mdc +0 -14
  12. plotly_print-0.2.2/.cursor/rules/global.mdc +0 -72
  13. plotly_print-0.2.2/AGENTS.md +0 -15
  14. {plotly_print-0.2.2 → plotly_print-0.2.3}/.cursor/rules/markdown.mdc +0 -0
  15. {plotly_print-0.2.2 → plotly_print-0.2.3}/.github/workflows/check-tag.yml +0 -0
  16. {plotly_print-0.2.2 → plotly_print-0.2.3}/.github/workflows/publish.yml +0 -0
  17. {plotly_print-0.2.2 → plotly_print-0.2.3}/.github/workflows/quality-checks.yml +0 -0
  18. {plotly_print-0.2.2 → plotly_print-0.2.3}/.github/workflows/version-check.yml +0 -0
  19. {plotly_print-0.2.2 → plotly_print-0.2.3}/.gitignore +0 -0
  20. {plotly_print-0.2.2 → plotly_print-0.2.3}/.mdformat.toml +0 -0
  21. {plotly_print-0.2.2 → plotly_print-0.2.3}/.pre-commit-config.yaml +0 -0
  22. {plotly_print-0.2.2 → plotly_print-0.2.3}/CONTRIBUTING.md +0 -0
  23. {plotly_print-0.2.2 → plotly_print-0.2.3}/LICENSE +0 -0
  24. {plotly_print-0.2.2 → plotly_print-0.2.3}/README.md +0 -0
  25. {plotly_print-0.2.2 → plotly_print-0.2.3}/THIRD_PARTY_NOTICES.md +0 -0
  26. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/benchmark_summary.json +0 -0
  27. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_annotations_arrowheads.png +0 -0
  28. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_annotations_long_arrows.png +0 -0
  29. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_bar.png +0 -0
  30. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_barpolar.png +0 -0
  31. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_box.png +0 -0
  32. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_candlestick.png +0 -0
  33. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_carpet.png +0 -0
  34. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_contour.png +0 -0
  35. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_funnel.png +0 -0
  36. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_funnelarea.png +0 -0
  37. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_heatmap.png +0 -0
  38. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_histogram.png +0 -0
  39. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_histogram2d.png +0 -0
  40. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_icicle.png +0 -0
  41. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_indicator.png +0 -0
  42. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_ohlc.png +0 -0
  43. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_parcats.png +0 -0
  44. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_pie.png +0 -0
  45. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_rotated_ticks_45.png +0 -0
  46. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_rotated_ticks_90.png +0 -0
  47. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_sankey.png +0 -0
  48. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_scatter_line.png +0 -0
  49. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_scatterpolar.png +0 -0
  50. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_scatterternary.png +0 -0
  51. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_sunburst.png +0 -0
  52. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_table.png +0 -0
  53. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_table_markup.png +0 -0
  54. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_treemap.png +0 -0
  55. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_violin.png +0 -0
  56. {plotly_print-0.2.2 → plotly_print-0.2.3}/assets/benchmarks/compare_waterfall.png +0 -0
  57. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/__init__.py +0 -0
  58. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/__main__.py +0 -0
  59. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/cases.py +0 -0
  60. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/collage.py +0 -0
  61. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/compare.py +0 -0
  62. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/report.py +0 -0
  63. {plotly_print-0.2.2 → plotly_print-0.2.3}/benchmark/runner.py +0 -0
  64. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/__init__.py +0 -0
  65. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/core.py +0 -0
  66. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/fonts.py +0 -0
  67. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/js/micro_dom.js +0 -0
  68. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/js/plotly_comprehensive_2d.js +0 -0
  69. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/plotly_hook.py +0 -0
  70. {plotly_print-0.2.2 → plotly_print-0.2.3}/src/plotly_print/raster.py +0 -0
  71. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_background.py +0 -0
  72. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_bar.py +0 -0
  73. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_cartesian_types.py +0 -0
  74. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_comprehensive_2d.py +0 -0
  75. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_heatmap.py +0 -0
  76. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_kaleido_visual.py +0 -0
  77. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_legend.py +0 -0
  78. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_pie.py +0 -0
  79. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_plotly_hook.py +0 -0
  80. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_raster.py +0 -0
  81. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_scatter.py +0 -0
  82. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_svg_path_geometry.py +0 -0
  83. {plotly_print-0.2.2 → plotly_print-0.2.3}/tests/test_svg_transforms.py +0 -0
@@ -0,0 +1,52 @@
1
+ ---
2
+ description: Coding standards for plotly-print (Python package + QuickJS micro-DOM)
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Project standards
7
+
8
+ Python package + QuickJS micro-DOM under `src/plotly_print/js/`. Source of truth: `pyproject.toml` (Python >= 3.12, uv). Package layout and entry points: `.cursor/rules/map.mdc`.
9
+
10
+ You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
11
+
12
+ ## Before writing code
13
+
14
+ Read the task and the code it touches, trace the real flow end to end, then stop at the first rung that holds:
15
+
16
+ 1. Does this need to be built at all? (YAGNI)
17
+ 2. Does it already exist in this codebase? Reuse it.
18
+ 3. Does the standard library already do this? Use it.
19
+ 4. Does an already-declared dependency solve it? Use it.
20
+ 5. Can this be one boring line? Make it one line.
21
+ 6. Only then: write the minimum code that works.
22
+
23
+ Bug fix = root cause, not symptom. Grep every caller of the function you touch and fix the shared function once.
24
+
25
+ ## Toolchain
26
+
27
+ - Do not add a dependency unless it is declared in `pyproject.toml` (or the lockfile). Prefer stdlib.
28
+ - Ruff via `pyproject.toml`. After Python edits: `poe format`. After Markdown: `poe format_docs`.
29
+ - No JS formatter/linter; match `micro_dom.js`. Never hand-edit vendored Plotly bundles.
30
+
31
+ ## Comments and docs
32
+
33
+ - No docstrings except on base/abstract contracts and complex functions that need them.
34
+ - No unnecessary comments; if needed, explain why, not what.
35
+ - No section-divider comments in classes/functions (allowed in test classes). Group by proximity.
36
+
37
+ ## Language
38
+
39
+ Shared: ASCII only in identifiers/comments/docs (no emojis). No unnecessary imports.
40
+
41
+ Python: type-hint every function/method; f-strings; join with `/` on `Path` (avoid extra wrappers).
42
+
43
+ ## Quality
44
+
45
+ - Deletion over addition. Boring over clever. Fewest files; shortest working diff after understanding the problem.
46
+ - No unrequested abstractions, boilerplate, or dependencies. Question complex asks: does Y cover it?
47
+ - Prefer the edge-case-correct stdlib approach when sizes match.
48
+ - Deliberate ceilings: `ponytail:` comment naming the ceiling and upgrade path.
49
+ - Non-trivial logic: one smallest test that fails if it breaks. Run `poe test` before claiming done.
50
+ - No secrets, `.env`, or local virtualenvs in commits.
51
+
52
+ Not lazy about: understanding the problem, trust-boundary validation, error handling that prevents data loss, security, anything explicitly requested.
@@ -10,7 +10,8 @@ First-party runtime JS lives under `src/plotly_print/js/`. It runs inside QuickJ
10
10
 
11
11
  ## Do not edit
12
12
 
13
- - `plotly_comprehensive_2d.js` and any other vendored Plotly/plotly.js bundle. Treat as opaque third-party input.
13
+ - `plotly_comprehensive_2d.js` (and any vendored Plotly bundle): opaque; in `.cursorignore`. Do not Read/Grep the whole file.
14
+ - Need a call shape: Shell `rg -n 'pattern' src/plotly_print/js/plotly_comprehensive_2d.js | head` (cap hits). Never open whole-file.
14
15
  - Only change first-party files such as `micro_dom.js`.
15
16
 
16
17
  ## Style (match `micro_dom.js`)
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: Package map — start here; avoid re-exploring the tree
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Package map
7
+
8
+ Browser-less Plotly static export: QuickJS + micro-DOM + vendored Plotly 2D → SVG → resvg raster.
9
+
10
+ ## Flow
11
+
12
+ `core.to_svg` / `to_image` / `write_image` → `engine.PrintEngine` (QuickJS) → eval `micro_dom.js` + vendored Plotly → SVG → `raster.py` for png/jpeg/webp.
13
+
14
+ `plotly_hook.register` patches Plotly `to_image` / `write_image`.
15
+
16
+ ## Files
17
+
18
+ | Path | Job |
19
+ |------|-----|
20
+ | `core.py` | Public API; figure → JSON |
21
+ | `engine.py` | QuickJS lifecycle; canvas→PNG; typed arrays |
22
+ | `fonts.py` | Text metrics callable into JS |
23
+ | `raster.py` | SVG → raster (resvg) |
24
+ | `plotly_hook.py` | Monkeypatch Plotly |
25
+ | `js/micro_dom.js` | First-party DOM/SVG stubs (**edit here**) |
26
+ | `js/plotly_comprehensive_2d.js` | Vendored; **never** Read/Grep whole file (`.cursorignore`) |
27
+
28
+ Start from this map. Open only listed files unless the task is outside `src/plotly_print/`.
29
+
30
+ ## Tests / commands / scratch
31
+
32
+ - `tests/test_svg_*.py` → micro-DOM; chart `test_*.py` → render; `test_kaleido_*` → visual parity
33
+ - `poe format` | `poe test` | `poe benchmark` | `poe format_docs`
34
+ - Scratch under `.artifacts/` (gitignored); do not commit
@@ -0,0 +1,2 @@
1
+ # Vendored Plotly.js (~2MB). Never index or open whole-file; capped rg via Shell if needed.
2
+ src/plotly_print/js/plotly_comprehensive_2d.js
@@ -0,0 +1,17 @@
1
+ # Agent instructions
2
+
3
+ plotly-print: Python package + QuickJS micro-DOM for browser-less Plotly static export.
4
+
5
+ ## Tooling
6
+
7
+ - **All AI IDEs:** start from this file and the package map.
8
+ - **Cursor:** rules in [`.cursor/rules/`](.cursor/rules/). Do not duplicate them here.
9
+
10
+ ## Standards (Cursor)
11
+
12
+ - Map (always-on): `.cursor/rules/map.mdc`
13
+ - Coding standards (always-on): `.cursor/rules/global.mdc`
14
+ - JavaScript (`src/plotly_print/js/`): `.cursor/rules/javascript.mdc`
15
+ - Markdown (`.md`): `.cursor/rules/markdown.mdc`
16
+
17
+ Vendored Plotly bundle is listed in `.cursorignore` — do not open it whole-file.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plotly-print
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Lightweight, browser-less static image exporter for Plotly
5
5
  Project-URL: Homepage, https://github.com/roman-kaczanowski/plotly-print
6
6
  Project-URL: Repository, https://github.com/roman-kaczanowski/plotly-print
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plotly-print"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Lightweight, browser-less static image exporter for Plotly"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -4,6 +4,7 @@ import base64
4
4
  import io
5
5
  import json
6
6
  import re
7
+ import struct
7
8
  import threading
8
9
  from pathlib import Path
9
10
  from typing import Any
@@ -69,6 +70,69 @@ def _encode_canvas_to_png(width: int, height: int, commands_json: str) -> str:
69
70
  return f'data:image/png;base64,{b64}'
70
71
 
71
72
 
73
+ # QuickJS throws on the out-of-bounds Uint8Array write plotly.js uses to drop
74
+ # base64 padding when decoding {'dtype', 'bdata'}. Expand those specs to lists
75
+ # before they reach the engine. Bytes are little-endian, matching the typed
76
+ # arrays plotly.js builds on the hosts this package runs on.
77
+ _TYPED_ARRAY_DTYPES = {
78
+ 'i1': 'b',
79
+ 'u1': 'B',
80
+ 'i2': 'h',
81
+ 'u2': 'H',
82
+ 'i4': 'i',
83
+ 'u4': 'I',
84
+ 'f4': 'f',
85
+ 'f8': 'd',
86
+ }
87
+
88
+
89
+ def _decode_typed_array(spec: dict[str, Any]) -> list[Any] | None:
90
+ dtype = spec.get('dtype')
91
+ bdata = spec.get('bdata')
92
+ if not isinstance(dtype, str) or not isinstance(bdata, str):
93
+ return None
94
+ code = _TYPED_ARRAY_DTYPES.get(dtype)
95
+ if code is None:
96
+ return None
97
+ shape = spec.get('shape')
98
+ if 'shape' in spec and not isinstance(shape, (str, int)):
99
+ return None
100
+
101
+ raw = base64.b64decode(bdata)
102
+ width = struct.calcsize(code)
103
+ if len(raw) % width:
104
+ raise ValueError(f'typed array byte length {len(raw)} is not a multiple of {width} for dtype {dtype}')
105
+ flat = list(struct.unpack(f'<{len(raw) // width}{code}', raw))
106
+ if shape is None:
107
+ return flat
108
+ dims = [int(part) for part in str(shape).split(',') if part.strip()]
109
+ return _reshape_row_major(flat, dims)
110
+
111
+
112
+ def _reshape_row_major(flat: list[Any], dims: list[int]) -> list[Any]:
113
+ size = 1
114
+ for dim in dims:
115
+ size *= dim
116
+ if size != len(flat):
117
+ joined = ','.join(str(dim) for dim in dims)
118
+ raise ValueError(f'typed array shape {joined} does not match {len(flat)} values')
119
+ if len(dims) <= 1:
120
+ return flat
121
+ span = size // dims[0]
122
+ return [_reshape_row_major(flat[index * span : (index + 1) * span], dims[1:]) for index in range(dims[0])]
123
+
124
+
125
+ def _expand_typed_arrays(value: Any) -> Any:
126
+ if isinstance(value, dict):
127
+ decoded = _decode_typed_array(value)
128
+ if decoded is not None:
129
+ return decoded
130
+ return {key: _expand_typed_arrays(item) for key, item in value.items()}
131
+ if isinstance(value, list):
132
+ return [_expand_typed_arrays(item) for item in value]
133
+ return value
134
+
135
+
72
136
  class PrintEngine:
73
137
  def __init__(self) -> None:
74
138
  self.font_manager = get_default_font_manager()
@@ -155,8 +219,8 @@ class PrintEngine:
155
219
  if height is not None:
156
220
  layout_copy['height'] = height
157
221
 
158
- data_json = json.dumps(data, cls=PlotlyJSONEncoder)
159
- layout_json = json.dumps(layout_copy, cls=PlotlyJSONEncoder)
222
+ data_json = json.dumps(_expand_typed_arrays(data), cls=PlotlyJSONEncoder)
223
+ layout_json = json.dumps(_expand_typed_arrays(layout_copy), cls=PlotlyJSONEncoder)
160
224
 
161
225
  self.context.eval(f'var __plotly_print_data = {json.dumps(data_json)};')
162
226
  self.context.eval(f'var __plotly_print_layout = {json.dumps(layout_json)};')
@@ -2,6 +2,7 @@
2
2
 
3
3
  import datetime as dt
4
4
  import decimal
5
+ import json
5
6
 
6
7
  import numpy as np
7
8
  import pandas as pd
@@ -9,6 +10,7 @@ import plotly.graph_objects as go
9
10
  import pytest
10
11
 
11
12
  import plotly_print
13
+ from plotly_print.engine import _expand_typed_arrays
12
14
 
13
15
 
14
16
  @pytest.mark.parametrize(
@@ -135,6 +137,62 @@ def test_non_json_values_render(name, fig):
135
137
  assert png.startswith(b'\x89PNG')
136
138
 
137
139
 
140
+ def test_typed_array_specs_expand_to_plain_values():
141
+ x = np.array([1.5, -2.25], dtype=np.float64)
142
+ y = np.array([-1, 2, 3, 4], dtype=np.int8)
143
+ color = np.array([0.25, 0.5], dtype=np.float32)
144
+ size = np.array([10, 20], dtype=np.uint16)
145
+ z = np.arange(4, dtype=np.float64).reshape(2, 2)
146
+ fig = go.Figure(
147
+ data=[
148
+ go.Scatter(x=x, y=y, marker={'color': color, 'size': size}),
149
+ go.Heatmap(z=z),
150
+ ]
151
+ )
152
+
153
+ expanded = _expand_typed_arrays(fig.to_dict())
154
+
155
+ assert expanded['data'][0]['x'] == x.tolist()
156
+ assert expanded['data'][0]['y'] == y.tolist()
157
+ assert expanded['data'][0]['marker']['color'] == color.tolist()
158
+ assert expanded['data'][0]['marker']['size'] == size.tolist()
159
+ assert expanded['data'][1]['z'] == z.tolist()
160
+
161
+
162
+ @pytest.mark.parametrize('source', ['figure', 'to_dict', 'json'])
163
+ def test_padded_numpy_bdata_renders(source):
164
+ # 22 float64 values are 176 bytes, which is not a multiple of 3, so the
165
+ # base64 payload has padding that used to crash QuickJS.
166
+ values = np.arange(22, dtype=float)
167
+ fig = go.Figure(
168
+ data=[go.Scatter(x=values, y=values, mode='lines+markers')],
169
+ layout=go.Layout(width=400, height=300, title='Bdata'),
170
+ )
171
+ if source == 'to_dict':
172
+ fig = fig.to_dict()
173
+ elif source == 'json':
174
+ fig = json.loads(fig.to_json())
175
+
176
+ svg = plotly_print.to_svg(fig)
177
+ assert '<svg' in svg
178
+ assert 'Bdata' in svg
179
+ assert 'class="point"' in svg
180
+
181
+
182
+ def test_padded_pandas_and_heatmap_render():
183
+ series = pd.Series(np.arange(22, dtype=float))
184
+ figures = [
185
+ go.Figure(data=[go.Scatter(x=series, y=series**2)], layout=go.Layout(width=400, height=300)),
186
+ go.Figure(
187
+ data=[go.Heatmap(z=np.arange(4, dtype=float).reshape(2, 2))], layout=go.Layout(width=400, height=300)
188
+ ),
189
+ go.Figure(data=[go.Bar(y=np.array([1, 2], dtype=np.int16))], layout=go.Layout(width=400, height=300)),
190
+ ]
191
+ for fig in figures:
192
+ svg = plotly_print.to_svg(fig)
193
+ assert '<svg' in svg
194
+
195
+
138
196
  def test_hooked_to_image_with_decimal_values():
139
197
  plotly_print.register()
140
198
 
@@ -536,7 +536,7 @@ wheels = [
536
536
 
537
537
  [[package]]
538
538
  name = "plotly-print"
539
- version = "0.2.2"
539
+ version = "0.2.3"
540
540
  source = { editable = "." }
541
541
  dependencies = [
542
542
  { name = "pillow" },
@@ -1,14 +0,0 @@
1
- ---
2
- description: Gitignored local output directory for agents
3
- alwaysApply: true
4
- ---
5
-
6
- # `.artifacts/`
7
-
8
- The repo root `.artifacts/` directory is **gitignored**. Use it for:
9
-
10
- - LLM research notes, comparison reports, scratch exports, and other **temporary local artifacts**
11
-
12
- Do not commit files under `.artifacts/`. Nothing there is shipped on PyPI or in the wheel.
13
-
14
- When generating research or large binary outputs for the user to review (outside the benchmark), prefer paths under `.artifacts/` unless they ask for another location.
@@ -1,72 +0,0 @@
1
- ---
2
- description: Coding standards for plotly-print (Python package + QuickJS micro-DOM)
3
- alwaysApply: true
4
- ---
5
-
6
- # Project standards
7
-
8
- plotly-print is a Python package (cookiecutter-python-seed layout) plus first-party QuickJS JavaScript under `src/plotly_print/js/` (micro-DOM for static Plotly rendering). Treat `pyproject.toml` as the source of truth for toolchain, Python version, and dependencies. Minimum Python version is 3.12. Use uv to lock, sync, build, and bump versions.
9
-
10
- JS-specific conventions: `.cursor/rules/javascript.mdc` (applies under `src/plotly_print/js/`).
11
-
12
- You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
13
-
14
- ## Before writing code
15
-
16
- Read the task and the code it touches, trace the real flow end to end, then stop at the first rung that holds:
17
-
18
- 1. Does this need to be built at all? (YAGNI)
19
- 2. Does it already exist in this codebase? Reuse it.
20
- 3. Does the standard library already do this? Use it.
21
- 4. Does an already-declared dependency solve it? Use it.
22
- 5. Can this be one boring line? Make it one line.
23
- 6. Only then: write the minimum code that works.
24
-
25
- Bug fix = root cause, not symptom. Grep every caller of the function you touch and fix the shared function once.
26
-
27
- ## Toolchain
28
-
29
- - Match existing layout: `src/` for packages, `tests/` for tests, application entry points as already configured.
30
- - Do not add a dependency unless it is declared in `pyproject.toml` (or the project's lockfile). Prefer stdlib over a new package.
31
- - Python style is enforced by Ruff. Follow `pyproject.toml`; the points below are the ones to apply consistently even when Ruff cannot.
32
- - After editing Python files, run `poe format`.
33
- - After editing Markdown files, run `poe format_docs`.
34
- - There is no JS formatter/linter task; match `micro_dom.js` style. Do not hand-edit vendored Plotly bundles.
35
-
36
- ## Comments and docs
37
-
38
- - No docstrings except on base/abstract classes and methods (the contract for implementors) and on complex functions where extra explanation genuinely helps.
39
- - No unnecessary comments. Code should be self-explanatory. If a comment is needed, explain why, not what.
40
- - No section-divider comments inside classes or functions. Group related methods by proximity. Dividers are allowed only in test classes.
41
-
42
- ## Language
43
-
44
- Shared (Python and JS):
45
-
46
- - ASCII only in identifiers, comments, and docs. No emojis. Non-ASCII letters are allowed only when the content is actually in another language (e.g. Polish, Russian).
47
- - No unnecessary imports.
48
-
49
- Python:
50
-
51
- - Type-hint every function and method. Do not nest annotations so deeply that they hurt readability.
52
- - Use f-strings for all string formatting.
53
- - Avoid unnecessary `Path` wrappers; join with `/` on `Path` objects.
54
-
55
- ## Quality
56
-
57
- - Use `poe format` / `poe format_docs` instead of hand-formatting style (Python/Markdown).
58
- - Deletion over addition. Boring over clever. Fewest files possible. Shortest working diff wins, after you understand the problem.
59
- - No abstractions, boilerplate, or dependencies that weren't requested.
60
- - Question complex requests: "Do you actually need X, or does Y cover it?"
61
- - When two stdlib approaches are the same size, pick the edge-case-correct one.
62
- - Mark deliberate simplifications that cut a real corner with a known ceiling (global lock, O(n^2) scan, naive heuristic) with a `ponytail:` comment naming the ceiling and upgrade path.
63
- - Non-trivial logic leaves one runnable check: the smallest test that fails if the logic breaks. Trivial one-liners need no new test. Run the project's test command before claiming work is done.
64
- - Do not commit secrets, `.env` files, or local virtualenvs.
65
-
66
- Not lazy about: understanding the problem, input validation at trust boundaries, error handling that prevents data loss, security, anything explicitly requested.
67
-
68
- ## Agents
69
-
70
- - Keep this file for conventions that should always apply.
71
- - Put path-specific Cursor rules in additional `.mdc` files in this directory.
72
- - Keep `AGENTS.md` as a short index that points here.
@@ -1,15 +0,0 @@
1
- # Agent instructions
2
-
3
- This project was generated from **cookiecutter-python-seed**, with first-party QuickJS JavaScript for Plotly static rendering.
4
-
5
- ## Tooling
6
-
7
- - **All AI IDEs:** follow this file.
8
- - **Cursor:** project rules live in [`.cursor/rules/`](.cursor/rules/). This file is the index; do not duplicate those rules here.
9
-
10
- ## Standards
11
-
12
- - Always-on: `.cursor/rules/global.mdc`
13
- - JavaScript (`src/plotly_print/js/`): `.cursor/rules/javascript.mdc`
14
- - Markdown (`.md` files): `.cursor/rules/markdown.mdc`
15
- - Local scratch output: `.cursor/rules/artifacts.mdc`
File without changes
File without changes
File without changes