datalayer-cells 0.0.2__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.

Potentially problematic release.


This version of datalayer-cells might be problematic. Click here for more details.

@@ -0,0 +1,14 @@
1
+ node_modules/
2
+ lib/
3
+ dist/
4
+ dist-npm/
5
+ bench/results/*.json
6
+ !bench/results/baselines.json
7
+ test-results/
8
+ playwright-report/
9
+ *-lock.json
10
+ __pycache__/
11
+ *.egg-info/
12
+ .pytest_cache/
13
+ build/
14
+ dist-py/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 Datalayer, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.5
2
+ Name: datalayer_cells
3
+ Version: 0.0.2
4
+ Summary: Datalayer Cells for Python: read and write Cells workbooks, as agents do, and the `datalayer cells` command
5
+ Project-URL: Homepage, https://github.com/datalayer/cells
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: datalayer-reactor>=1.3.1
10
+ Requires-Dist: typer>=0.12
11
+ Provides-Extra: test
12
+ Requires-Dist: pytest>=8; extra == 'test'
13
+ Description-Content-Type: text/markdown
14
+
15
+ [![Datalayer](https://images.datalayer.io/legacy/datalayer-25.svg)](https://datalayer.ai)
16
+
17
+ [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
18
+
19
+ # ☰ 🦠 Datalayer Cells
20
+
21
+ A spreadsheet drawn on canvas and exposed as a React component, over a
22
+ headless model that also runs in Node and in workers. Cells is built on
23
+ [Reactor](https://github.com/datalayer/reactor): every extension point (formula
24
+ functions, cell types, commands, importers and exporters, data sources, the
25
+ collaboration provider) is a Reactor contribution point, and the built-in
26
+ features are Reactor plugins contributing through the same points.
27
+
28
+ > Work in progress: nothing is published yet.
29
+
30
+ ## Packages
31
+
32
+ | Import | What |
33
+ | ------------------------- | ------------------------------------------------------- |
34
+ | `@datalayer/cells` | the React component and hooks; re-exports `./core` |
35
+ | `@datalayer/cells/core` | the headless model: no React, no DOM |
36
+ | `@datalayer/cells/csv` | CSV and TSV in and out |
37
+ | `@datalayer/cells/xlsx` | XLSX in and out, loaded lazily |
38
+ | `@datalayer/cells/collab` | real-time collaboration over Loro |
39
+ | `@datalayer/cells/chrome` | toolbar, formula bar and sheet tabs |
40
+ | `@datalayer/cells/plugin` | views for a Reactor shell |
41
+
42
+ ## Python
43
+
44
+ `datalayer_cells` reads and writes the same workbooks, with the same
45
+ validation, for agents and scripts, and adds the `datalayer cells` command
46
+ (also `datalayer-cells` on its own):
47
+
48
+ ```python
49
+ from datalayer_cells import Workbook
50
+
51
+ wb = Workbook.load("budget.cells.json")
52
+ wb.read("Summary!A1:C4") # rows of values
53
+ wb.write("B2", [[12, "=B1*2"]]) # formulas are kept as typed
54
+ wb.save("budget.cells.json")
55
+ ```
56
+
57
+ ```bash
58
+ datalayer cells show budget.cells.json
59
+ datalayer cells get budget.cells.json 'Summary!A1:C4'
60
+ datalayer cells set budget.cells.json B2 12 '=B1*2'
61
+ datalayer cells export budget.cells.json --format csv
62
+ ```
63
+
64
+ ## Examples
65
+
66
+ ```bash
67
+ make examples # http://127.0.0.1:3077/
68
+ ```
69
+
70
+ A page with a menu of examples, from a few values to collaboration, each with
71
+ its source. What is not built yet says so, and names the milestone that
72
+ brings it.
73
+
74
+ ## Development
75
+
76
+ `@datalayer/reactor` resolves as a package: from npm, or from the workspace in
77
+ the Datalayer monorepo (install at its root).
78
+
79
+ ```bash
80
+ npm install
81
+ npm run lint # Prettier and the licence header on every file
82
+ npm run typecheck # everything, tests and examples included
83
+ npm test # unit tests (vitest, two workers), the React tests under React 19 and 18.3
84
+ npm run build # lib/
85
+ npm run bench # Node benches into bench/results/
86
+ python -m pytest # the Python package (pip install -e ".[test]" first)
87
+ npm run size # bundle sizes against their budgets
88
+ ```
89
+
90
+ Browser benches and end-to-end tests run on the system Chrome, headless:
91
+
92
+ ```bash
93
+ env -u WAYLAND_DISPLAY -u DISPLAY node scripts/bench.mjs --browser
94
+ ```
95
+
96
+ ## Credits
97
+
98
+ Cells was designed after reading [x-spreadsheet](https://github.com/myliang/x-spreadsheet)
99
+ (MIT) and [o-spreadsheet](https://github.com/odoo/o-spreadsheet) (LGPLv3) for
100
+ ideas; no code of either is included. See [THIRD_PARTY.md](THIRD_PARTY.md).
101
+
102
+ ## License
103
+
104
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,90 @@
1
+ [![Datalayer](https://images.datalayer.io/legacy/datalayer-25.svg)](https://datalayer.ai)
2
+
3
+ [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
4
+
5
+ # ☰ 🦠 Datalayer Cells
6
+
7
+ A spreadsheet drawn on canvas and exposed as a React component, over a
8
+ headless model that also runs in Node and in workers. Cells is built on
9
+ [Reactor](https://github.com/datalayer/reactor): every extension point (formula
10
+ functions, cell types, commands, importers and exporters, data sources, the
11
+ collaboration provider) is a Reactor contribution point, and the built-in
12
+ features are Reactor plugins contributing through the same points.
13
+
14
+ > Work in progress: nothing is published yet.
15
+
16
+ ## Packages
17
+
18
+ | Import | What |
19
+ | ------------------------- | ------------------------------------------------------- |
20
+ | `@datalayer/cells` | the React component and hooks; re-exports `./core` |
21
+ | `@datalayer/cells/core` | the headless model: no React, no DOM |
22
+ | `@datalayer/cells/csv` | CSV and TSV in and out |
23
+ | `@datalayer/cells/xlsx` | XLSX in and out, loaded lazily |
24
+ | `@datalayer/cells/collab` | real-time collaboration over Loro |
25
+ | `@datalayer/cells/chrome` | toolbar, formula bar and sheet tabs |
26
+ | `@datalayer/cells/plugin` | views for a Reactor shell |
27
+
28
+ ## Python
29
+
30
+ `datalayer_cells` reads and writes the same workbooks, with the same
31
+ validation, for agents and scripts, and adds the `datalayer cells` command
32
+ (also `datalayer-cells` on its own):
33
+
34
+ ```python
35
+ from datalayer_cells import Workbook
36
+
37
+ wb = Workbook.load("budget.cells.json")
38
+ wb.read("Summary!A1:C4") # rows of values
39
+ wb.write("B2", [[12, "=B1*2"]]) # formulas are kept as typed
40
+ wb.save("budget.cells.json")
41
+ ```
42
+
43
+ ```bash
44
+ datalayer cells show budget.cells.json
45
+ datalayer cells get budget.cells.json 'Summary!A1:C4'
46
+ datalayer cells set budget.cells.json B2 12 '=B1*2'
47
+ datalayer cells export budget.cells.json --format csv
48
+ ```
49
+
50
+ ## Examples
51
+
52
+ ```bash
53
+ make examples # http://127.0.0.1:3077/
54
+ ```
55
+
56
+ A page with a menu of examples, from a few values to collaboration, each with
57
+ its source. What is not built yet says so, and names the milestone that
58
+ brings it.
59
+
60
+ ## Development
61
+
62
+ `@datalayer/reactor` resolves as a package: from npm, or from the workspace in
63
+ the Datalayer monorepo (install at its root).
64
+
65
+ ```bash
66
+ npm install
67
+ npm run lint # Prettier and the licence header on every file
68
+ npm run typecheck # everything, tests and examples included
69
+ npm test # unit tests (vitest, two workers), the React tests under React 19 and 18.3
70
+ npm run build # lib/
71
+ npm run bench # Node benches into bench/results/
72
+ python -m pytest # the Python package (pip install -e ".[test]" first)
73
+ npm run size # bundle sizes against their budgets
74
+ ```
75
+
76
+ Browser benches and end-to-end tests run on the system Chrome, headless:
77
+
78
+ ```bash
79
+ env -u WAYLAND_DISPLAY -u DISPLAY node scripts/bench.mjs --browser
80
+ ```
81
+
82
+ ## Credits
83
+
84
+ Cells was designed after reading [x-spreadsheet](https://github.com/myliang/x-spreadsheet)
85
+ (MIT) and [o-spreadsheet](https://github.com/odoo/o-spreadsheet) (LGPLv3) for
86
+ ideas; no code of either is included. See [THIRD_PARTY.md](THIRD_PARTY.md).
87
+
88
+ ## License
89
+
90
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2022-2026 Datalayer, Inc.
2
+ #
3
+ # MIT License
4
+
5
+ """Datalayer Cells for Python: read and write Cells workbooks, as agents do.
6
+
7
+ The ``CellsWorkbook`` JSON format with the same validation as
8
+ ``@datalayer/cells``, and the ``datalayer cells`` command. Live rooms
9
+ (the Loro schema) come with the collaboration milestone.
10
+ """
11
+
12
+ from .__version__ import __version__
13
+ from .format import CELL_ERRORS, FORMAT_VERSION, FormatError, validate_workbook
14
+ from .workbook import Workbook, column_index, column_letters, parse_a1, parse_input, parse_zone
15
+
16
+ __all__ = [
17
+ "CELL_ERRORS",
18
+ "FORMAT_VERSION",
19
+ "FormatError",
20
+ "Workbook",
21
+ "__version__",
22
+ "column_index",
23
+ "column_letters",
24
+ "parse_a1",
25
+ "parse_input",
26
+ "parse_zone",
27
+ "validate_workbook",
28
+ ]
@@ -0,0 +1,7 @@
1
+ # Copyright (c) 2022-2026 Datalayer, Inc.
2
+ #
3
+ # MIT License
4
+
5
+ """The version of datalayer_cells; dev/bump_version.py moves it with package.json."""
6
+
7
+ __version__ = "0.0.2"
@@ -0,0 +1,167 @@
1
+ # Copyright (c) 2022-2026 Datalayer, Inc.
2
+ #
3
+ # MIT License
4
+
5
+ """``datalayer cells``: read and write Cells workbooks from a terminal.
6
+
7
+ Two doors to the same commands, as Datalayer's other packages have:
8
+ installed beside the Datalayer CLI, the ``cells`` group joins the
9
+ ``datalayer`` command through the ``datalayer.cli`` entry point; on its own,
10
+ ``datalayer-cells`` runs it.
11
+
12
+ datalayer cells show budget.cells.json # the sheets and their used ranges
13
+ datalayer cells get budget.cells.json 'Summary!A1:C4'
14
+ datalayer cells set budget.cells.json B2 12 '=B1*2'
15
+ datalayer cells export budget.cells.json --sheet Summary --format csv
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import csv
21
+ import io
22
+ import json
23
+ from pathlib import Path
24
+ from typing import Any, List, Optional
25
+
26
+ import typer
27
+ from reactor import PluginManifest
28
+
29
+ from .__version__ import __version__
30
+ from .format import FormatError
31
+ from .workbook import Workbook, column_letters, parse_input
32
+
33
+ app = typer.Typer(
34
+ name="cells",
35
+ help="Read and write Datalayer Cells workbooks (.cells.json).",
36
+ no_args_is_help=True,
37
+ )
38
+
39
+
40
+ def _load(path: Path) -> Workbook:
41
+ try:
42
+ return Workbook.load(path)
43
+ except (OSError, FormatError) as error:
44
+ typer.echo(f"Cannot read {path}: {error}", err=True)
45
+ raise typer.Exit(1) from error
46
+
47
+
48
+ def _show(value: Any) -> str:
49
+ if value is None:
50
+ return ""
51
+ if isinstance(value, dict):
52
+ return str(value.get("error"))
53
+ if isinstance(value, bool):
54
+ return "TRUE" if value else "FALSE"
55
+ return str(value)
56
+
57
+
58
+ @app.command()
59
+ def show(path: Path) -> None:
60
+ """The sheets of a workbook, and the range each one uses."""
61
+ wb = _load(path)
62
+ for sheet in wb.to_json()["sheets"]:
63
+ rows = [int(r) for r in sheet["rows"]]
64
+ cols = [int(c) for row in sheet["rows"].values() for c in row["cells"]]
65
+ used = f"A1:{column_letters(max(cols))}{max(rows) + 1}" if rows and cols else "empty"
66
+ count = sum(len(row["cells"]) for row in sheet["rows"].values())
67
+ typer.echo(f"{sheet['name']}\t{used}\t{count} cells")
68
+
69
+
70
+ @app.command()
71
+ def get(path: Path, ref: str, formulas: bool = typer.Option(False, "--formulas", help="Show formulas as typed.")) -> None:
72
+ """The values (or formulas) of a range, tab-separated."""
73
+ wb = _load(path)
74
+ try:
75
+ rows = wb.read(ref)
76
+ typed = wb.formulas(ref) if formulas else None
77
+ except (KeyError, ValueError) as error:
78
+ typer.echo(str(error).strip('"'), err=True)
79
+ raise typer.Exit(1) from error
80
+ for r, row in enumerate(rows):
81
+ typer.echo("\t".join(typed[r][c] or _show(v) if typed else _show(v) for c, v in enumerate(row)))
82
+
83
+
84
+ @app.command(name="set")
85
+ def set_(
86
+ path: Path,
87
+ ref: str,
88
+ values: List[str] = typer.Argument(..., help="Values for a row from REF, typed as in a cell: 12, TRUE, =A1*2, 'text."),
89
+ ) -> None:
90
+ """Write a row of values from a cell, as typing them would, and save."""
91
+ wb = _load(path)
92
+ row: list[Any] = []
93
+ for text in values:
94
+ parsed = parse_input(text)
95
+ row.append(f"={parsed['f']}" if "f" in parsed else parsed.get("v"))
96
+ try:
97
+ wb.write(ref, [row])
98
+ except (KeyError, ValueError) as error:
99
+ typer.echo(str(error).strip('"'), err=True)
100
+ raise typer.Exit(1) from error
101
+ wb.save(path)
102
+ typer.echo(f"Wrote {len(row)} cell(s) from {ref}.")
103
+
104
+
105
+ @app.command()
106
+ def export(
107
+ path: Path,
108
+ sheet: Optional[str] = typer.Option(None, help="The sheet to export (the first by default)."),
109
+ format: str = typer.Option("csv", help="csv or json."),
110
+ ) -> None:
111
+ """A sheet as CSV (values) or the workbook as canonical JSON, on standard output."""
112
+ wb = _load(path)
113
+ if format == "json":
114
+ typer.echo(json.dumps(wb.to_json(), indent=2))
115
+ return
116
+ if format != "csv":
117
+ typer.echo(f"No export format '{format}': csv or json.", err=True)
118
+ raise typer.Exit(1)
119
+ data = wb.to_json()
120
+ names = [s["name"] for s in data["sheets"]]
121
+ chosen = sheet or names[0]
122
+ match = [s for s in data["sheets"] if s["name"].lower() == chosen.lower()]
123
+ if not match:
124
+ typer.echo(f"No sheet '{chosen}'; the workbook has {', '.join(names)}.", err=True)
125
+ raise typer.Exit(1)
126
+ rows = match[0]["rows"]
127
+ if not rows:
128
+ return
129
+ height = max(int(r) for r in rows) + 1
130
+ width = max(int(c) for row in rows.values() for c in row["cells"]) + 1
131
+ quoted = "'" + match[0]["name"].replace("'", "''") + "'"
132
+ values = wb.read(f"{quoted}!A1:{column_letters(width - 1)}{height}")
133
+ out = io.StringIO()
134
+ csv.writer(out, lineterminator="\n").writerows([[_show(v) for v in row] for row in values])
135
+ typer.echo(out.getvalue(), nl=False)
136
+
137
+
138
+ @app.command()
139
+ def version() -> None:
140
+ """The version of datalayer_cells."""
141
+ typer.echo(__version__)
142
+
143
+
144
+ class CellsCliExtension:
145
+ """The plugin: registers the ``cells`` group into the host CLI."""
146
+
147
+ def provide_cli(self, cli: "typer.Typer") -> None:
148
+ cli.add_typer(app, name="cells")
149
+
150
+
151
+ manifest = PluginManifest(
152
+ name="cells-cli",
153
+ version=__version__,
154
+ description="The `cells` command group: show, get, set, export.",
155
+ author="Datalayer",
156
+ tags=["cli", "cells"],
157
+ )
158
+
159
+
160
+ def plugin() -> tuple[PluginManifest, CellsCliExtension]:
161
+ """What the ``datalayer.cli`` entry point resolves to."""
162
+ return manifest, CellsCliExtension()
163
+
164
+
165
+ def main() -> None:
166
+ """The ``datalayer-cells`` console script: the same group, on its own."""
167
+ app()