sheetforge-mcp 0.2.1__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.
- sheetforge_mcp-0.2.1/.gitignore +6 -0
- sheetforge_mcp-0.2.1/LICENSE +21 -0
- sheetforge_mcp-0.2.1/PKG-INFO +178 -0
- sheetforge_mcp-0.2.1/README.md +163 -0
- sheetforge_mcp-0.2.1/excel_mcp/__main__.py +47 -0
- sheetforge_mcp-0.2.1/excel_mcp/calculations.py +55 -0
- sheetforge_mcp-0.2.1/excel_mcp/cell_utils.py +54 -0
- sheetforge_mcp-0.2.1/excel_mcp/cell_validation.py +179 -0
- sheetforge_mcp-0.2.1/excel_mcp/chart.py +251 -0
- sheetforge_mcp-0.2.1/excel_mcp/data.py +384 -0
- sheetforge_mcp-0.2.1/excel_mcp/exceptions.py +35 -0
- sheetforge_mcp-0.2.1/excel_mcp/formatting.py +247 -0
- sheetforge_mcp-0.2.1/excel_mcp/pivot.py +265 -0
- sheetforge_mcp-0.2.1/excel_mcp/server.py +940 -0
- sheetforge_mcp-0.2.1/excel_mcp/sheet.py +463 -0
- sheetforge_mcp-0.2.1/excel_mcp/tables.py +67 -0
- sheetforge_mcp-0.2.1/excel_mcp/validation.py +235 -0
- sheetforge_mcp-0.2.1/excel_mcp/workbook.py +138 -0
- sheetforge_mcp-0.2.1/pyproject.toml +30 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Haris
|
|
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,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sheetforge-mcp
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: SheetForge MCP for reading, writing, and reshaping Excel workbooks
|
|
5
|
+
Author: SheetForge
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: fastmcp<3.0.0,>=2.0.0
|
|
9
|
+
Requires-Dist: mcp[cli]>=1.10.1
|
|
10
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
11
|
+
Requires-Dist: typer>=0.16.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# SheetForge MCP
|
|
17
|
+
|
|
18
|
+
SheetForge MCP exposes `.xlsx` workbook operations over the Model Context Protocol. It uses `openpyxl` under the hood, so MCP clients can inspect and modify Excel files without launching Microsoft Excel or LibreOffice.
|
|
19
|
+
|
|
20
|
+
Package name: `sheetforge-mcp`
|
|
21
|
+
CLI command: `sheetforge-mcp`
|
|
22
|
+
|
|
23
|
+
## What This Project Covers
|
|
24
|
+
|
|
25
|
+
- workbook creation and metadata
|
|
26
|
+
- worksheet creation, renaming, copying, and deletion
|
|
27
|
+
- structured reads, compact table reads, and cell search
|
|
28
|
+
- row, column, and range mutations
|
|
29
|
+
- formulas and validation checks
|
|
30
|
+
- formatting, merges, and conditional formatting
|
|
31
|
+
- native Excel tables, charts, and pivot summaries
|
|
32
|
+
- `stdio`, `streamable-http`, and deprecated `sse` transports
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Python `3.10+`
|
|
37
|
+
- `.xlsx` workbooks
|
|
38
|
+
- either `uvx` or a local package install
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### Stdio
|
|
43
|
+
|
|
44
|
+
Use `stdio` when the MCP client starts the server locally.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uvx sheetforge-mcp stdio
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"excel": {
|
|
54
|
+
"command": "uvx",
|
|
55
|
+
"args": ["sheetforge-mcp", "stdio"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Streamable HTTP
|
|
62
|
+
|
|
63
|
+
Use `streamable-http` when you want a long-running local or remote server process.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
EXCEL_FILES_PATH=/path/to/excel-files uvx sheetforge-mcp streamable-http
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Default endpoint:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
http://127.0.0.1:8017/mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Example client config:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"excel": {
|
|
81
|
+
"url": "http://127.0.0.1:8017/mcp"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### SSE
|
|
88
|
+
|
|
89
|
+
SSE is kept for compatibility, but new integrations should prefer `streamable-http`.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
EXCEL_FILES_PATH=/path/to/excel-files uvx sheetforge-mcp sse
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Default endpoint:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
http://127.0.0.1:8017/sse
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## File Path Rules
|
|
102
|
+
|
|
103
|
+
- In `stdio` mode, `filepath` values must be absolute paths.
|
|
104
|
+
- In `streamable-http` and `sse` mode, relative paths are resolved under `EXCEL_FILES_PATH`.
|
|
105
|
+
- Absolute paths are accepted in every transport.
|
|
106
|
+
- In `streamable-http` and `sse` mode, the server creates `EXCEL_FILES_PATH` automatically if it does not exist.
|
|
107
|
+
|
|
108
|
+
## Environment Variables
|
|
109
|
+
|
|
110
|
+
| Variable | Default | Used by | Purpose |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `FASTMCP_HOST` | `127.0.0.1` | HTTP and SSE | Bind address for the server process |
|
|
113
|
+
| `FASTMCP_PORT` | `8017` | HTTP and SSE | Port for the server process |
|
|
114
|
+
| `EXCEL_FILES_PATH` | `./excel_files` | HTTP and SSE | Base directory for relative workbook paths |
|
|
115
|
+
|
|
116
|
+
## Tooling Overview
|
|
117
|
+
|
|
118
|
+
The server currently registers 28 MCP tools across these groups:
|
|
119
|
+
|
|
120
|
+
- workbook overview: `create_workbook`, `create_worksheet`, `get_workbook_metadata`, `list_all_sheets`
|
|
121
|
+
- data access: `read_data_from_excel`, `read_excel_as_table`, `search_in_sheet`, `write_data_to_excel`
|
|
122
|
+
- worksheet and range changes: `copy_worksheet`, `delete_worksheet`, `rename_worksheet`, `copy_range`, `delete_range`, `insert_rows`, `insert_columns`, `delete_sheet_rows`, `delete_sheet_columns`
|
|
123
|
+
- formatting and layout: `format_range`, `merge_cells`, `unmerge_cells`, `get_merged_cells`
|
|
124
|
+
- formulas and validation: `apply_formula`, `validate_formula_syntax`, `validate_excel_range`, `get_data_validation_info`
|
|
125
|
+
- analysis and structure: `create_table`, `create_chart`, `create_pivot_table`
|
|
126
|
+
|
|
127
|
+
The three most agent-friendly read tools are:
|
|
128
|
+
|
|
129
|
+
- `list_all_sheets`: quick workbook inventory with sheet sizes and emptiness flags
|
|
130
|
+
- `read_excel_as_table`: compact `headers + rows` output for structured datasets
|
|
131
|
+
- `search_in_sheet`: exact or partial value search across a worksheet
|
|
132
|
+
|
|
133
|
+
See [TOOLS.md](TOOLS.md) for the full reference.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
Install dependencies:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uv sync --extra dev
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Run tests:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
uv run --extra dev pytest -q
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Run the package locally:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
uv run sheetforge-mcp stdio
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Release Flow
|
|
156
|
+
|
|
157
|
+
- GitHub releases run a build verification workflow only.
|
|
158
|
+
- PyPI publishing is a separate manual workflow, so releases do not create a failing deployment before Trusted Publisher is configured for the package.
|
|
159
|
+
|
|
160
|
+
## Repository Layout
|
|
161
|
+
|
|
162
|
+
- `src/excel_mcp/server.py`: MCP server, transport setup, and tool registration
|
|
163
|
+
- `src/excel_mcp/workbook.py`: workbook lifecycle helpers and workbook metadata
|
|
164
|
+
- `src/excel_mcp/data.py`: read, write, table, and search helpers
|
|
165
|
+
- `src/excel_mcp/sheet.py`: worksheet and range mutations
|
|
166
|
+
- `tests/`: regression tests for workbook handling and public behavior
|
|
167
|
+
- `manifest.json`: packaged MCP bundle metadata
|
|
168
|
+
- `docs/index.html`: static project landing page
|
|
169
|
+
|
|
170
|
+
## Notes For Integrators
|
|
171
|
+
|
|
172
|
+
- `stdio` mode is careful not to write non-protocol text to `stdout`.
|
|
173
|
+
- Most read-oriented tools return JSON strings, but a few older tools still return plain string representations for compatibility.
|
|
174
|
+
- `read_data_from_excel(..., preview_only=True)` limits the response to the first 10 rows in the selected range and marks the payload as truncated when applicable.
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# SheetForge MCP
|
|
2
|
+
|
|
3
|
+
SheetForge MCP exposes `.xlsx` workbook operations over the Model Context Protocol. It uses `openpyxl` under the hood, so MCP clients can inspect and modify Excel files without launching Microsoft Excel or LibreOffice.
|
|
4
|
+
|
|
5
|
+
Package name: `sheetforge-mcp`
|
|
6
|
+
CLI command: `sheetforge-mcp`
|
|
7
|
+
|
|
8
|
+
## What This Project Covers
|
|
9
|
+
|
|
10
|
+
- workbook creation and metadata
|
|
11
|
+
- worksheet creation, renaming, copying, and deletion
|
|
12
|
+
- structured reads, compact table reads, and cell search
|
|
13
|
+
- row, column, and range mutations
|
|
14
|
+
- formulas and validation checks
|
|
15
|
+
- formatting, merges, and conditional formatting
|
|
16
|
+
- native Excel tables, charts, and pivot summaries
|
|
17
|
+
- `stdio`, `streamable-http`, and deprecated `sse` transports
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Python `3.10+`
|
|
22
|
+
- `.xlsx` workbooks
|
|
23
|
+
- either `uvx` or a local package install
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### Stdio
|
|
28
|
+
|
|
29
|
+
Use `stdio` when the MCP client starts the server locally.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uvx sheetforge-mcp stdio
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"excel": {
|
|
39
|
+
"command": "uvx",
|
|
40
|
+
"args": ["sheetforge-mcp", "stdio"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Streamable HTTP
|
|
47
|
+
|
|
48
|
+
Use `streamable-http` when you want a long-running local or remote server process.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
EXCEL_FILES_PATH=/path/to/excel-files uvx sheetforge-mcp streamable-http
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Default endpoint:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
http://127.0.0.1:8017/mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Example client config:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"excel": {
|
|
66
|
+
"url": "http://127.0.0.1:8017/mcp"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### SSE
|
|
73
|
+
|
|
74
|
+
SSE is kept for compatibility, but new integrations should prefer `streamable-http`.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
EXCEL_FILES_PATH=/path/to/excel-files uvx sheetforge-mcp sse
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Default endpoint:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
http://127.0.0.1:8017/sse
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## File Path Rules
|
|
87
|
+
|
|
88
|
+
- In `stdio` mode, `filepath` values must be absolute paths.
|
|
89
|
+
- In `streamable-http` and `sse` mode, relative paths are resolved under `EXCEL_FILES_PATH`.
|
|
90
|
+
- Absolute paths are accepted in every transport.
|
|
91
|
+
- In `streamable-http` and `sse` mode, the server creates `EXCEL_FILES_PATH` automatically if it does not exist.
|
|
92
|
+
|
|
93
|
+
## Environment Variables
|
|
94
|
+
|
|
95
|
+
| Variable | Default | Used by | Purpose |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `FASTMCP_HOST` | `127.0.0.1` | HTTP and SSE | Bind address for the server process |
|
|
98
|
+
| `FASTMCP_PORT` | `8017` | HTTP and SSE | Port for the server process |
|
|
99
|
+
| `EXCEL_FILES_PATH` | `./excel_files` | HTTP and SSE | Base directory for relative workbook paths |
|
|
100
|
+
|
|
101
|
+
## Tooling Overview
|
|
102
|
+
|
|
103
|
+
The server currently registers 28 MCP tools across these groups:
|
|
104
|
+
|
|
105
|
+
- workbook overview: `create_workbook`, `create_worksheet`, `get_workbook_metadata`, `list_all_sheets`
|
|
106
|
+
- data access: `read_data_from_excel`, `read_excel_as_table`, `search_in_sheet`, `write_data_to_excel`
|
|
107
|
+
- worksheet and range changes: `copy_worksheet`, `delete_worksheet`, `rename_worksheet`, `copy_range`, `delete_range`, `insert_rows`, `insert_columns`, `delete_sheet_rows`, `delete_sheet_columns`
|
|
108
|
+
- formatting and layout: `format_range`, `merge_cells`, `unmerge_cells`, `get_merged_cells`
|
|
109
|
+
- formulas and validation: `apply_formula`, `validate_formula_syntax`, `validate_excel_range`, `get_data_validation_info`
|
|
110
|
+
- analysis and structure: `create_table`, `create_chart`, `create_pivot_table`
|
|
111
|
+
|
|
112
|
+
The three most agent-friendly read tools are:
|
|
113
|
+
|
|
114
|
+
- `list_all_sheets`: quick workbook inventory with sheet sizes and emptiness flags
|
|
115
|
+
- `read_excel_as_table`: compact `headers + rows` output for structured datasets
|
|
116
|
+
- `search_in_sheet`: exact or partial value search across a worksheet
|
|
117
|
+
|
|
118
|
+
See [TOOLS.md](TOOLS.md) for the full reference.
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
Install dependencies:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv sync --extra dev
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Run tests:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uv run --extra dev pytest -q
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Run the package locally:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uv run sheetforge-mcp stdio
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Release Flow
|
|
141
|
+
|
|
142
|
+
- GitHub releases run a build verification workflow only.
|
|
143
|
+
- PyPI publishing is a separate manual workflow, so releases do not create a failing deployment before Trusted Publisher is configured for the package.
|
|
144
|
+
|
|
145
|
+
## Repository Layout
|
|
146
|
+
|
|
147
|
+
- `src/excel_mcp/server.py`: MCP server, transport setup, and tool registration
|
|
148
|
+
- `src/excel_mcp/workbook.py`: workbook lifecycle helpers and workbook metadata
|
|
149
|
+
- `src/excel_mcp/data.py`: read, write, table, and search helpers
|
|
150
|
+
- `src/excel_mcp/sheet.py`: worksheet and range mutations
|
|
151
|
+
- `tests/`: regression tests for workbook handling and public behavior
|
|
152
|
+
- `manifest.json`: packaged MCP bundle metadata
|
|
153
|
+
- `docs/index.html`: static project landing page
|
|
154
|
+
|
|
155
|
+
## Notes For Integrators
|
|
156
|
+
|
|
157
|
+
- `stdio` mode is careful not to write non-protocol text to `stdout`.
|
|
158
|
+
- Most read-oriented tools return JSON strings, but a few older tools still return plain string representations for compatibility.
|
|
159
|
+
- `read_data_from_excel(..., preview_only=True)` limits the response to the first 10 rows in the selected range and marks the payload as truncated when applicable.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import traceback
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from .server import run_sse, run_stdio, run_streamable_http
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="SheetForge MCP")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _run_server(start_fn: Callable[[], None]) -> None:
|
|
13
|
+
"""Run a server command without writing protocol-breaking output to stdout."""
|
|
14
|
+
exit_code = 0
|
|
15
|
+
try:
|
|
16
|
+
start_fn()
|
|
17
|
+
except KeyboardInterrupt:
|
|
18
|
+
typer.echo("Shutting down server...", err=True)
|
|
19
|
+
exit_code = 130
|
|
20
|
+
except Exception as e:
|
|
21
|
+
typer.echo(f"Error: {e}", err=True)
|
|
22
|
+
traceback.print_exc(file=sys.stderr)
|
|
23
|
+
exit_code = 1
|
|
24
|
+
finally:
|
|
25
|
+
typer.echo("Service stopped.", err=True)
|
|
26
|
+
|
|
27
|
+
if exit_code:
|
|
28
|
+
raise typer.Exit(code=exit_code)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def sse():
|
|
33
|
+
"""Start SheetForge MCP in SSE mode."""
|
|
34
|
+
_run_server(run_sse)
|
|
35
|
+
|
|
36
|
+
@app.command()
|
|
37
|
+
def streamable_http():
|
|
38
|
+
"""Start SheetForge MCP in streamable HTTP mode."""
|
|
39
|
+
_run_server(run_streamable_http)
|
|
40
|
+
|
|
41
|
+
@app.command()
|
|
42
|
+
def stdio():
|
|
43
|
+
"""Start SheetForge MCP in stdio mode."""
|
|
44
|
+
_run_server(run_stdio)
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
app()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from .workbook import safe_workbook
|
|
5
|
+
from .cell_utils import validate_cell_reference
|
|
6
|
+
from .exceptions import ValidationError, CalculationError
|
|
7
|
+
from .validation import validate_formula
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
def apply_formula(
|
|
12
|
+
filepath: str,
|
|
13
|
+
sheet_name: str,
|
|
14
|
+
cell: str,
|
|
15
|
+
formula: str
|
|
16
|
+
) -> dict[str, Any]:
|
|
17
|
+
"""Apply any Excel formula to a cell."""
|
|
18
|
+
try:
|
|
19
|
+
if not validate_cell_reference(cell):
|
|
20
|
+
raise ValidationError(f"Invalid cell reference: {cell}")
|
|
21
|
+
|
|
22
|
+
# Ensure formula starts with =
|
|
23
|
+
if not formula.startswith('='):
|
|
24
|
+
formula = f'={formula}'
|
|
25
|
+
|
|
26
|
+
# Validate formula syntax
|
|
27
|
+
is_valid, message = validate_formula(formula)
|
|
28
|
+
if not is_valid:
|
|
29
|
+
raise CalculationError(f"Invalid formula syntax: {message}")
|
|
30
|
+
|
|
31
|
+
with safe_workbook(filepath, save=True) as wb:
|
|
32
|
+
if sheet_name not in wb.sheetnames:
|
|
33
|
+
raise ValidationError(f"Sheet '{sheet_name}' not found")
|
|
34
|
+
|
|
35
|
+
sheet = wb[sheet_name]
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Apply formula to the cell
|
|
39
|
+
cell_obj = sheet[cell]
|
|
40
|
+
cell_obj.value = formula
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise CalculationError(f"Failed to apply formula to cell: {str(e)}")
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"message": f"Applied formula '{formula}' to cell {cell}",
|
|
46
|
+
"cell": cell,
|
|
47
|
+
"formula": formula
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
except (ValidationError, CalculationError) as e:
|
|
51
|
+
logger.error(str(e))
|
|
52
|
+
raise
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.error(f"Failed to apply formula: {e}")
|
|
55
|
+
raise CalculationError(str(e))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from openpyxl.utils import column_index_from_string
|
|
4
|
+
|
|
5
|
+
def parse_cell_range(
|
|
6
|
+
cell_ref: str,
|
|
7
|
+
end_ref: str | None = None
|
|
8
|
+
) -> tuple[int, int, int | None, int | None]:
|
|
9
|
+
"""Parse Excel cell reference into row and column indices."""
|
|
10
|
+
if end_ref:
|
|
11
|
+
start_cell = cell_ref
|
|
12
|
+
end_cell = end_ref
|
|
13
|
+
else:
|
|
14
|
+
start_cell = cell_ref
|
|
15
|
+
end_cell = None
|
|
16
|
+
|
|
17
|
+
match = re.match(r"([A-Z]+)([0-9]+)", start_cell.upper())
|
|
18
|
+
if not match:
|
|
19
|
+
raise ValueError(f"Invalid cell reference: {start_cell}")
|
|
20
|
+
col_str, row_str = match.groups()
|
|
21
|
+
start_row = int(row_str)
|
|
22
|
+
start_col = column_index_from_string(col_str)
|
|
23
|
+
|
|
24
|
+
if end_cell:
|
|
25
|
+
match = re.match(r"([A-Z]+)([0-9]+)", end_cell.upper())
|
|
26
|
+
if not match:
|
|
27
|
+
raise ValueError(f"Invalid cell reference: {end_cell}")
|
|
28
|
+
col_str, row_str = match.groups()
|
|
29
|
+
end_row = int(row_str)
|
|
30
|
+
end_col = column_index_from_string(col_str)
|
|
31
|
+
else:
|
|
32
|
+
end_row = None
|
|
33
|
+
end_col = None
|
|
34
|
+
|
|
35
|
+
return start_row, start_col, end_row, end_col
|
|
36
|
+
|
|
37
|
+
def validate_cell_reference(cell_ref: str) -> bool:
|
|
38
|
+
"""Validate Excel cell reference format (e.g., 'A1', 'BC123')"""
|
|
39
|
+
if not cell_ref:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Split into column and row parts
|
|
43
|
+
col = row = ""
|
|
44
|
+
for c in cell_ref:
|
|
45
|
+
if c.isalpha():
|
|
46
|
+
if row: # Letters after numbers not allowed
|
|
47
|
+
return False
|
|
48
|
+
col += c
|
|
49
|
+
elif c.isdigit():
|
|
50
|
+
row += c
|
|
51
|
+
else:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
return bool(col and row)
|