excel-grapher 0.1.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.
- excel_grapher-0.1.0/.gitignore +27 -0
- excel_grapher-0.1.0/PKG-INFO +164 -0
- excel_grapher-0.1.0/README.md +131 -0
- excel_grapher-0.1.0/excel_grapher/__init__.py +146 -0
- excel_grapher-0.1.0/excel_grapher/core/__init__.py +71 -0
- excel_grapher-0.1.0/excel_grapher/core/address_keys.py +131 -0
- excel_grapher-0.1.0/excel_grapher/core/addressing.py +276 -0
- excel_grapher-0.1.0/excel_grapher/core/cell_types.py +245 -0
- excel_grapher-0.1.0/excel_grapher/core/coercions.py +126 -0
- excel_grapher-0.1.0/excel_grapher/core/excel_function_meta.py +44 -0
- excel_grapher-0.1.0/excel_grapher/core/expr_eval.py +375 -0
- excel_grapher-0.1.0/excel_grapher/core/formula_ast.py +458 -0
- excel_grapher-0.1.0/excel_grapher/core/formula_normalization.py +197 -0
- excel_grapher-0.1.0/excel_grapher/core/operators.py +194 -0
- excel_grapher-0.1.0/excel_grapher/core/types.py +59 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/__init__.py +32 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/errors.py +25 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/evaluator.py +611 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/__init__.py +30 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/info.py +12 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/logic.py +14 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/lookup.py +22 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/math.py +39 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/reference.py +14 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/functions/text.py +22 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/helpers.py +63 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/name_utils.py +76 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/parser.py +45 -0
- excel_grapher-0.1.0/excel_grapher/evaluator/types.py +5 -0
- excel_grapher-0.1.0/excel_grapher/exporter/__init__.py +86 -0
- excel_grapher-0.1.0/excel_grapher/exporter/codegen.py +2272 -0
- excel_grapher-0.1.0/excel_grapher/exporter/embed.py +421 -0
- excel_grapher-0.1.0/excel_grapher/exporter/lightweight_viz.py +583 -0
- excel_grapher-0.1.0/excel_grapher/exporter/web_viz_layout.py +276 -0
- excel_grapher-0.1.0/excel_grapher/grapher/__init__.py +121 -0
- excel_grapher-0.1.0/excel_grapher/grapher/blank_ranges.py +106 -0
- excel_grapher-0.1.0/excel_grapher/grapher/builder.py +1505 -0
- excel_grapher-0.1.0/excel_grapher/grapher/cache.py +429 -0
- excel_grapher-0.1.0/excel_grapher/grapher/compression.py +130 -0
- excel_grapher-0.1.0/excel_grapher/grapher/dependency_provenance.py +57 -0
- excel_grapher-0.1.0/excel_grapher/grapher/dynamic_refs.py +3664 -0
- excel_grapher-0.1.0/excel_grapher/grapher/export.py +248 -0
- excel_grapher-0.1.0/excel_grapher/grapher/formula_label.py +14 -0
- excel_grapher-0.1.0/excel_grapher/grapher/graph.py +859 -0
- excel_grapher-0.1.0/excel_grapher/grapher/guard.py +199 -0
- excel_grapher-0.1.0/excel_grapher/grapher/lightweight_viz.py +1495 -0
- excel_grapher-0.1.0/excel_grapher/grapher/lightweight_viz_template.html +806 -0
- excel_grapher-0.1.0/excel_grapher/grapher/node.py +93 -0
- excel_grapher-0.1.0/excel_grapher/grapher/parser.py +1306 -0
- excel_grapher-0.1.0/excel_grapher/grapher/provenance_collect.py +766 -0
- excel_grapher-0.1.0/excel_grapher/grapher/resolver.py +391 -0
- excel_grapher-0.1.0/excel_grapher/grapher/subgraph.py +178 -0
- excel_grapher-0.1.0/excel_grapher/grapher/target_expansion.py +147 -0
- excel_grapher-0.1.0/excel_grapher/grapher/type_analysis_cache.py +443 -0
- excel_grapher-0.1.0/excel_grapher/grapher/validation.py +182 -0
- excel_grapher-0.1.0/excel_grapher/runtime/__init__.py +11 -0
- excel_grapher-0.1.0/excel_grapher/runtime/cache.py +332 -0
- excel_grapher-0.1.0/excel_grapher/runtime/info.py +23 -0
- excel_grapher-0.1.0/excel_grapher/runtime/logic.py +56 -0
- excel_grapher-0.1.0/excel_grapher/runtime/lookup.py +299 -0
- excel_grapher-0.1.0/excel_grapher/runtime/math.py +362 -0
- excel_grapher-0.1.0/excel_grapher/runtime/offset_runtime.py +164 -0
- excel_grapher-0.1.0/excel_grapher/runtime/reference.py +81 -0
- excel_grapher-0.1.0/excel_grapher/runtime/text.py +167 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/__init__.py +159 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/bindings_codegen.py +63 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/canonical.py +16 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/compute_codegen.py +213 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/docstring_renderers.py +359 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/docstrings.py +342 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/input_series.py +69 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/issues.py +24 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/load.py +176 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/normalize.py +119 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/output_series.py +63 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/ranges.py +121 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/records_types.py +9 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/resolve.py +593 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/schema.py +76 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/series_binding.schema.json +671 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/setter_codegen.py +323 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/types.py +84 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/validate.py +320 -0
- excel_grapher-0.1.0/excel_grapher/series_bindings/versions.py +29 -0
- excel_grapher-0.1.0/pyproject.toml +127 -0
- excel_grapher-0.1.0/tests/README.md +13 -0
- excel_grapher-0.1.0/tests/__init__.py +1 -0
- excel_grapher-0.1.0/tests/fixtures/series_bindings/borvelia_primary_balance.yaml +59 -0
- excel_grapher-0.1.0/tests/fixtures/series_bindings/matrix_country_block_1_1_0.yaml +42 -0
- excel_grapher-0.1.0/tests/fixtures/series_bindings/shard_assumptions.yaml +24 -0
- excel_grapher-0.1.0/tests/fixtures/series_bindings/shard_borvelia_input.yaml +40 -0
- excel_grapher-0.1.0/tests/fixtures/series_bindings/shard_borvelia_output.yaml +40 -0
- excel_grapher-0.1.0/tests/fixtures/series_bindings/shard_inputs.yaml +24 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_evaluator_excel_primitives_parity.py +56 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_golden_master.py +258 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_ifna_parity.py +49 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_index_parity.py +101 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_integration_lic_dsf.py +174 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_lic_dsf_chart_parity.py +145 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_lic_dsf_gdp_bps_chart_parity.py +155 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_lookup_parity.py +117 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_match_parity.py +54 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_missing_cell_parity.py +49 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_numbervalue_parity.py +102 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_offset_parity.py +93 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_parity_circular_references.py +170 -0
- excel_grapher-0.1.0/tests/integration/evaluator/test_workbook_iterative_settings_integration.py +148 -0
- excel_grapher-0.1.0/tests/integration/evaluator/utils/lic_dsf_chart_targets.py +202 -0
- excel_grapher-0.1.0/tests/integration/evaluator/utils/test_lic_dsf_chart_targets.py +45 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_codegen_export_modular.py +216 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_codegen_export_no_diagnostics.py +129 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_codegen_integration.py +428 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_golden_parity.py +63 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_ty_regression_large_export_choose.py +70 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_ty_regression_large_export_sum_range.py +70 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_web_viz_api.py +223 -0
- excel_grapher-0.1.0/tests/integration/exporter/test_web_viz_payload_perf.py +39 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_blank_ranges.py +129 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_conditional_functions.py +85 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_dependency_graph_build.py +394 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_dependency_graph_targets.py +218 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_export_graphviz.py +99 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_export_lightweight_viz_html.py +160 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_export_mermaid.py +82 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_export_networkx.py +70 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_graph_cache_json.py +167 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_guarded_edges_cycle_detection.py +233 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_guarded_edges_exports.py +74 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_named_ranges.py +159 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_smoke_api.py +34 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_type_analysis_cache_integration.py +684 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_validation_calc_settings.py +43 -0
- excel_grapher-0.1.0/tests/integration/grapher/test_validation_calcchain.py +124 -0
- excel_grapher-0.1.0/tests/integration/user_flows/test_column_bindings.py +138 -0
- excel_grapher-0.1.0/tests/integration/user_flows/test_extraction_basics.py +321 -0
- excel_grapher-0.1.0/tests/integration/user_flows/test_series_bindings.py +140 -0
- excel_grapher-0.1.0/tests/integration/user_flows/test_series_bindings_codegen.py +300 -0
- excel_grapher-0.1.0/tests/integration/user_flows/test_series_bindings_output_codegen.py +201 -0
- excel_grapher-0.1.0/tests/integration/user_flows/utils.py +67 -0
- excel_grapher-0.1.0/tests/integration/utils/parity_harness.py +195 -0
- excel_grapher-0.1.0/tests/integration/utils/test_parity_harness.py +111 -0
- excel_grapher-0.1.0/tests/unit/core/test_address_keys.py +76 -0
- excel_grapher-0.1.0/tests/unit/core/test_addressing.py +213 -0
- excel_grapher-0.1.0/tests/unit/core/test_cell_type_env.py +76 -0
- excel_grapher-0.1.0/tests/unit/core/test_constraints_mapping.py +171 -0
- excel_grapher-0.1.0/tests/unit/core/test_formula_ast_and_expr_eval.py +110 -0
- excel_grapher-0.1.0/tests/unit/core/test_formula_normalization.py +26 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_evaluator.py +232 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_functions.py +658 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_helpers.py +164 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_incremental.py +196 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_name_utils.py +204 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_parser.py +166 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_types.py +36 -0
- excel_grapher-0.1.0/tests/unit/evaluator/test_utils_helpers.py +95 -0
- excel_grapher-0.1.0/tests/unit/exporter/test_codegen.py +1041 -0
- excel_grapher-0.1.0/tests/unit/exporter/test_codegen_edge_cases.py +537 -0
- excel_grapher-0.1.0/tests/unit/exporter/test_codegen_input_serialization.py +69 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_dynamic_ref_tracer.py +278 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_dynamic_refs.py +3344 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_extractor.py +248 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_formula_normalizer.py +154 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_graph_api.py +416 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_graph_compression.py +261 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_graph_serialization.py +220 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_guard_constraints.py +66 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_import_boundaries.py +42 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_list_constraint_candidates.py +402 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_normalized_formula.py +150 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_parser.py +188 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_path_induced_subgraph.py +193 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_perf_optimizations.py +253 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_resolver.py +59 -0
- excel_grapher-0.1.0/tests/unit/grapher/test_type_analysis_cache.py +565 -0
- excel_grapher-0.1.0/tests/unit/scripts/test_build_workflow_cytoscape_preset.py +93 -0
- excel_grapher-0.1.0/tests/unit/scripts/test_build_workflow_dot.py +158 -0
- excel_grapher-0.1.0/tests/unit/scripts/test_build_workflow_pre_render.py +67 -0
- excel_grapher-0.1.0/tests/unit/scripts/test_check_version_bump.py +170 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_compute_codegen.py +434 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_docstrings.py +527 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_input_series.py +88 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_keyless_scalar.py +175 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_load.py +102 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_normalize.py +134 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_output_series.py +41 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_ranges.py +70 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_resolve.py +181 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_resolve_output.py +133 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_schema.py +173 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_setter_codegen.py +387 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_validate.py +93 -0
- excel_grapher-0.1.0/tests/unit/series_bindings/test_versions.py +42 -0
- excel_grapher-0.1.0/tests/unit/test_package_boundaries.py +83 -0
- excel_grapher-0.1.0/tests/utils/__init__.py +13 -0
- excel_grapher-0.1.0/tests/utils/_helpers.py +64 -0
- excel_grapher-0.1.0/tests/utils/discover_formula_cells.py +56 -0
- excel_grapher-0.1.0/tests/utils/excel_workbook_parity.py +203 -0
- excel_grapher-0.1.0/tests/utils/modify_and_recalculate.py +182 -0
- excel_grapher-0.1.0/tests/utils/read_cell.py +76 -0
- excel_grapher-0.1.0/tests/utils/test_modify_and_recalculate.py +24 -0
- excel_grapher-0.1.0/tests/utils/workbook_xml.py +55 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Coding agents
|
|
13
|
+
.claude
|
|
14
|
+
.codex
|
|
15
|
+
*.prof.txt
|
|
16
|
+
*.prof
|
|
17
|
+
|
|
18
|
+
# Secrets
|
|
19
|
+
.env
|
|
20
|
+
# Great Docs build directory (ephemeral, do not commit)
|
|
21
|
+
great-docs/
|
|
22
|
+
|
|
23
|
+
# Great Docs versioned-build artifacts
|
|
24
|
+
_great_docs_build/
|
|
25
|
+
.great-docs-build/
|
|
26
|
+
.great-docs-cache/
|
|
27
|
+
.great-docs/
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: excel-grapher
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build and analyze dependency graphs from Excel workbooks
|
|
5
|
+
Project-URL: Homepage, https://github.com/Teal-Insights/excel-grapher
|
|
6
|
+
Project-URL: Repository, https://github.com/Teal-Insights/excel-grapher
|
|
7
|
+
Author-email: Teal Insights <info@tealinsights.com>
|
|
8
|
+
License: Proprietary
|
|
9
|
+
Keywords: dependency,excel,formula,graph,spreadsheet,transpiler
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: fastpyxl
|
|
22
|
+
Requires-Dist: jsonschema>=4.26.0
|
|
23
|
+
Requires-Dist: numpy>=1.24
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: graphviz>=0.20; extra == 'all'
|
|
27
|
+
Requires-Dist: networkx>=3.0; extra == 'all'
|
|
28
|
+
Provides-Extra: networkx
|
|
29
|
+
Requires-Dist: networkx>=3.0; extra == 'networkx'
|
|
30
|
+
Provides-Extra: viz
|
|
31
|
+
Requires-Dist: graphviz>=0.20; extra == 'viz'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
## `excel-grapher`
|
|
35
|
+
|
|
36
|
+
Build and analyze dependency graphs from Excel workbooks, **evaluate formulas with Excel semantics**, and **export standalone Python code**.
|
|
37
|
+
|
|
38
|
+
**Documentation:** [https://teal-insights.github.io/excel-grapher/](https://teal-insights.github.io/excel-grapher/)
|
|
39
|
+
|
|
40
|
+
### Why this exists
|
|
41
|
+
|
|
42
|
+
- **Transpilation support**: trace formula dependencies to enable Excel → Python translation.
|
|
43
|
+
- **Interpretability**: visualize and sanity-check spreadsheet logic (GraphViz, Mermaid, NetworkX).
|
|
44
|
+
- **Performance-minded**: focuses on targeted dependency closure from specific output cells/ranges.
|
|
45
|
+
- **Excel semantics in Python**: run workbook logic in-process with a full Excel-like evaluator.
|
|
46
|
+
- **Exportable**: emit standalone Python packages that embed only the runtime surface you need.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Library layout
|
|
51
|
+
|
|
52
|
+
The unified distribution is `excel-grapher` and exposes a single import package, `excel_grapher`, with five main subpackages:
|
|
53
|
+
|
|
54
|
+
- `excel_grapher/core/` — shared semantic types, coercions, and scalar operators.
|
|
55
|
+
- `excel_grapher/runtime/` — Excel-equivalent function implementations and runtime semantics.
|
|
56
|
+
- `excel_grapher/grapher/` — workbook loading, graph extraction, and visualization logic.
|
|
57
|
+
- `excel_grapher/evaluator/` — `FormulaEvaluator`: an Excel emulator for recomputing formulas in the extracted graph in Python.
|
|
58
|
+
- `excel_grapher/exporter/` — `CodeGenerator`: an transpiler for exporting the extracted graph as a standalone Python library.
|
|
59
|
+
|
|
60
|
+
Typical imports:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from excel_grapher.grapher import create_dependency_graph, DependencyGraph
|
|
64
|
+
from excel_grapher.evaluator import FormulaEvaluator
|
|
65
|
+
from excel_grapher.exporter import CodeGenerator
|
|
66
|
+
from excel_grapher.core import XlError # and other shared types, if needed
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### Installation
|
|
72
|
+
|
|
73
|
+
This is a proprietary package. Install from the private GitHub repository:
|
|
74
|
+
|
|
75
|
+
**Using `uv` (recommended):**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Basic install
|
|
79
|
+
uv add git+https://github.com/Teal-Insights/excel-grapher
|
|
80
|
+
|
|
81
|
+
# With NetworkX support
|
|
82
|
+
uv add "excel-grapher[networkx] @ git+https://github.com/Teal-Insights/excel-grapher"
|
|
83
|
+
|
|
84
|
+
# With all optional dependencies
|
|
85
|
+
uv add "excel-grapher[all] @ git+https://github.com/Teal-Insights/excel-grapher"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Using `pip`:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install git+https://github.com/Teal-Insights/excel-grapher
|
|
92
|
+
|
|
93
|
+
# With extras:
|
|
94
|
+
pip install "excel-grapher[networkx] @ git+https://github.com/Teal-Insights/excel-grapher"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> **Note:** You must have access to the Teal-Insights GitHub organization and appropriate SSH keys or tokens configured.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### High-level usage
|
|
102
|
+
|
|
103
|
+
The library supports a three-stage pipeline:
|
|
104
|
+
|
|
105
|
+
1. **Build a dependency graph** from an Excel workbook (`excel_grapher.grapher`).
|
|
106
|
+
2. **Evaluate formulas with Excel semantics** over that graph (`excel_grapher.evaluator.FormulaEvaluator`).
|
|
107
|
+
3. **Export standalone Python code** that embeds only the required runtime surface (`excel_grapher.exporter.CodeGenerator`).
|
|
108
|
+
|
|
109
|
+
A minimal end‑to‑end example:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from pathlib import Path
|
|
113
|
+
|
|
114
|
+
from excel_grapher.grapher import create_dependency_graph
|
|
115
|
+
from excel_grapher.evaluator import FormulaEvaluator
|
|
116
|
+
from excel_grapher.exporter import CodeGenerator
|
|
117
|
+
|
|
118
|
+
workbook_path = Path("model.xlsx")
|
|
119
|
+
targets = ["Sheet1!A10"]
|
|
120
|
+
|
|
121
|
+
# 1) Build a dependency graph
|
|
122
|
+
graph = create_dependency_graph(workbook_path, targets, load_values=True)
|
|
123
|
+
print(len(graph)) # number of visited nodes
|
|
124
|
+
|
|
125
|
+
# 2) Evaluate with Excel semantics
|
|
126
|
+
with FormulaEvaluator(graph) as ev:
|
|
127
|
+
results = ev.evaluate(targets)
|
|
128
|
+
|
|
129
|
+
# 3) Export standalone Python code
|
|
130
|
+
code = CodeGenerator(graph).generate(targets)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Series bindings
|
|
134
|
+
|
|
135
|
+
Optional sidecar manifests (`.bindings.yaml`) declare structured input/output APIs for **exported**
|
|
136
|
+
code — `set_*` and `compute_*` functions over `Records`. See the
|
|
137
|
+
[Series bindings guide](https://teal-insights.github.io/excel-grapher/user-guide/series-bindings.html)
|
|
138
|
+
and [Code export](https://teal-insights.github.io/excel-grapher/user-guide/export.html) guide.
|
|
139
|
+
|
|
140
|
+
### User guide
|
|
141
|
+
|
|
142
|
+
Detailed documentation lives in the [User Guide](https://teal-insights.github.io/excel-grapher/user-guide/dependency-graphs.html):
|
|
143
|
+
|
|
144
|
+
| Topic | Page |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| Dependency graphs | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/dependency-graphs.html) |
|
|
147
|
+
| Visualization | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/visualization.html) |
|
|
148
|
+
| Formula evaluation | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/evaluator.html) |
|
|
149
|
+
| End-to-end demo | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/end-to-end-demo.html) |
|
|
150
|
+
| Series bindings | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/series-bindings.html) |
|
|
151
|
+
| Code export | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/export.html) |
|
|
152
|
+
| Parity testing | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/parity-testing.html) |
|
|
153
|
+
| Contributing | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/contributing.html) |
|
|
154
|
+
|
|
155
|
+
### Examples
|
|
156
|
+
|
|
157
|
+
Hands-on walkthroughs (Markdown on GitHub):
|
|
158
|
+
|
|
159
|
+
| Topic | Walkthrough |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| Graph extraction | [extraction_basics.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/micro_workbooks/extraction_basics.md) |
|
|
162
|
+
| Code generation | [codegen_basics.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/micro_workbooks/codegen_basics.md) |
|
|
163
|
+
| Series bindings | [series_bindings.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/micro_workbooks/series_bindings.md) |
|
|
164
|
+
| Induced graph | [induced_graph.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/induced_graph/induced_graph.md) |
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
## `excel-grapher`
|
|
2
|
+
|
|
3
|
+
Build and analyze dependency graphs from Excel workbooks, **evaluate formulas with Excel semantics**, and **export standalone Python code**.
|
|
4
|
+
|
|
5
|
+
**Documentation:** [https://teal-insights.github.io/excel-grapher/](https://teal-insights.github.io/excel-grapher/)
|
|
6
|
+
|
|
7
|
+
### Why this exists
|
|
8
|
+
|
|
9
|
+
- **Transpilation support**: trace formula dependencies to enable Excel → Python translation.
|
|
10
|
+
- **Interpretability**: visualize and sanity-check spreadsheet logic (GraphViz, Mermaid, NetworkX).
|
|
11
|
+
- **Performance-minded**: focuses on targeted dependency closure from specific output cells/ranges.
|
|
12
|
+
- **Excel semantics in Python**: run workbook logic in-process with a full Excel-like evaluator.
|
|
13
|
+
- **Exportable**: emit standalone Python packages that embed only the runtime surface you need.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
### Library layout
|
|
18
|
+
|
|
19
|
+
The unified distribution is `excel-grapher` and exposes a single import package, `excel_grapher`, with five main subpackages:
|
|
20
|
+
|
|
21
|
+
- `excel_grapher/core/` — shared semantic types, coercions, and scalar operators.
|
|
22
|
+
- `excel_grapher/runtime/` — Excel-equivalent function implementations and runtime semantics.
|
|
23
|
+
- `excel_grapher/grapher/` — workbook loading, graph extraction, and visualization logic.
|
|
24
|
+
- `excel_grapher/evaluator/` — `FormulaEvaluator`: an Excel emulator for recomputing formulas in the extracted graph in Python.
|
|
25
|
+
- `excel_grapher/exporter/` — `CodeGenerator`: an transpiler for exporting the extracted graph as a standalone Python library.
|
|
26
|
+
|
|
27
|
+
Typical imports:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from excel_grapher.grapher import create_dependency_graph, DependencyGraph
|
|
31
|
+
from excel_grapher.evaluator import FormulaEvaluator
|
|
32
|
+
from excel_grapher.exporter import CodeGenerator
|
|
33
|
+
from excel_grapher.core import XlError # and other shared types, if needed
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### Installation
|
|
39
|
+
|
|
40
|
+
This is a proprietary package. Install from the private GitHub repository:
|
|
41
|
+
|
|
42
|
+
**Using `uv` (recommended):**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Basic install
|
|
46
|
+
uv add git+https://github.com/Teal-Insights/excel-grapher
|
|
47
|
+
|
|
48
|
+
# With NetworkX support
|
|
49
|
+
uv add "excel-grapher[networkx] @ git+https://github.com/Teal-Insights/excel-grapher"
|
|
50
|
+
|
|
51
|
+
# With all optional dependencies
|
|
52
|
+
uv add "excel-grapher[all] @ git+https://github.com/Teal-Insights/excel-grapher"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Using `pip`:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install git+https://github.com/Teal-Insights/excel-grapher
|
|
59
|
+
|
|
60
|
+
# With extras:
|
|
61
|
+
pip install "excel-grapher[networkx] @ git+https://github.com/Teal-Insights/excel-grapher"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> **Note:** You must have access to the Teal-Insights GitHub organization and appropriate SSH keys or tokens configured.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### High-level usage
|
|
69
|
+
|
|
70
|
+
The library supports a three-stage pipeline:
|
|
71
|
+
|
|
72
|
+
1. **Build a dependency graph** from an Excel workbook (`excel_grapher.grapher`).
|
|
73
|
+
2. **Evaluate formulas with Excel semantics** over that graph (`excel_grapher.evaluator.FormulaEvaluator`).
|
|
74
|
+
3. **Export standalone Python code** that embeds only the required runtime surface (`excel_grapher.exporter.CodeGenerator`).
|
|
75
|
+
|
|
76
|
+
A minimal end‑to‑end example:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from pathlib import Path
|
|
80
|
+
|
|
81
|
+
from excel_grapher.grapher import create_dependency_graph
|
|
82
|
+
from excel_grapher.evaluator import FormulaEvaluator
|
|
83
|
+
from excel_grapher.exporter import CodeGenerator
|
|
84
|
+
|
|
85
|
+
workbook_path = Path("model.xlsx")
|
|
86
|
+
targets = ["Sheet1!A10"]
|
|
87
|
+
|
|
88
|
+
# 1) Build a dependency graph
|
|
89
|
+
graph = create_dependency_graph(workbook_path, targets, load_values=True)
|
|
90
|
+
print(len(graph)) # number of visited nodes
|
|
91
|
+
|
|
92
|
+
# 2) Evaluate with Excel semantics
|
|
93
|
+
with FormulaEvaluator(graph) as ev:
|
|
94
|
+
results = ev.evaluate(targets)
|
|
95
|
+
|
|
96
|
+
# 3) Export standalone Python code
|
|
97
|
+
code = CodeGenerator(graph).generate(targets)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Series bindings
|
|
101
|
+
|
|
102
|
+
Optional sidecar manifests (`.bindings.yaml`) declare structured input/output APIs for **exported**
|
|
103
|
+
code — `set_*` and `compute_*` functions over `Records`. See the
|
|
104
|
+
[Series bindings guide](https://teal-insights.github.io/excel-grapher/user-guide/series-bindings.html)
|
|
105
|
+
and [Code export](https://teal-insights.github.io/excel-grapher/user-guide/export.html) guide.
|
|
106
|
+
|
|
107
|
+
### User guide
|
|
108
|
+
|
|
109
|
+
Detailed documentation lives in the [User Guide](https://teal-insights.github.io/excel-grapher/user-guide/dependency-graphs.html):
|
|
110
|
+
|
|
111
|
+
| Topic | Page |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| Dependency graphs | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/dependency-graphs.html) |
|
|
114
|
+
| Visualization | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/visualization.html) |
|
|
115
|
+
| Formula evaluation | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/evaluator.html) |
|
|
116
|
+
| End-to-end demo | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/end-to-end-demo.html) |
|
|
117
|
+
| Series bindings | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/series-bindings.html) |
|
|
118
|
+
| Code export | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/export.html) |
|
|
119
|
+
| Parity testing | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/parity-testing.html) |
|
|
120
|
+
| Contributing | [Read guide](https://teal-insights.github.io/excel-grapher/user-guide/contributing.html) |
|
|
121
|
+
|
|
122
|
+
### Examples
|
|
123
|
+
|
|
124
|
+
Hands-on walkthroughs (Markdown on GitHub):
|
|
125
|
+
|
|
126
|
+
| Topic | Walkthrough |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| Graph extraction | [extraction_basics.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/micro_workbooks/extraction_basics.md) |
|
|
129
|
+
| Code generation | [codegen_basics.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/micro_workbooks/codegen_basics.md) |
|
|
130
|
+
| Series bindings | [series_bindings.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/micro_workbooks/series_bindings.md) |
|
|
131
|
+
| Induced graph | [induced_graph.md](https://github.com/Teal-Insights/excel-grapher/blob/main/examples/induced_graph/induced_graph.md) |
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""excel_grapher: Build and analyze dependency graphs from Excel workbooks; evaluate formulas; export standalone Python.
|
|
2
|
+
|
|
3
|
+
Public API is namespaced by role: excel_grapher.grapher, excel_grapher.evaluator, excel_grapher.exporter.
|
|
4
|
+
Convenience re-exports below keep existing call patterns working.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from . import exporter, grapher, series_bindings
|
|
8
|
+
from .evaluator import (
|
|
9
|
+
CellValue,
|
|
10
|
+
ExcelRange,
|
|
11
|
+
FormulaEvaluator,
|
|
12
|
+
ParseError,
|
|
13
|
+
XlError,
|
|
14
|
+
)
|
|
15
|
+
from .exporter import CodeGenerator, to_web_viz_payload
|
|
16
|
+
from .grapher import (
|
|
17
|
+
GRAPH_CACHE_SCHEMA_VERSION,
|
|
18
|
+
And,
|
|
19
|
+
CacheValidationPolicy,
|
|
20
|
+
Compare,
|
|
21
|
+
CycleError,
|
|
22
|
+
CycleReport,
|
|
23
|
+
DependencyGraph,
|
|
24
|
+
DynamicRefConfig,
|
|
25
|
+
DynamicRefError,
|
|
26
|
+
DynamicRefLimits,
|
|
27
|
+
DynamicRefTraceEvent,
|
|
28
|
+
DynamicRefTraceFn,
|
|
29
|
+
FromWorkbook,
|
|
30
|
+
GreaterThanCell,
|
|
31
|
+
GuardCellRef,
|
|
32
|
+
GuardExpr,
|
|
33
|
+
LightweightVizPayload,
|
|
34
|
+
Literal,
|
|
35
|
+
LocalForceSubgraph,
|
|
36
|
+
Node,
|
|
37
|
+
NodeHook,
|
|
38
|
+
NodeKey,
|
|
39
|
+
Not,
|
|
40
|
+
NotEqualCell,
|
|
41
|
+
Or,
|
|
42
|
+
RealBetween,
|
|
43
|
+
RealIntervalDomain,
|
|
44
|
+
ValidationResult,
|
|
45
|
+
WorkbookCalcSettings,
|
|
46
|
+
build_graph_cache_meta,
|
|
47
|
+
build_graph_cache_meta_portable,
|
|
48
|
+
create_dependency_graph,
|
|
49
|
+
format_cell_key,
|
|
50
|
+
format_key,
|
|
51
|
+
get_calc_settings,
|
|
52
|
+
list_dynamic_ref_constraint_candidates,
|
|
53
|
+
needs_quoting,
|
|
54
|
+
normalize_blank_range_specs,
|
|
55
|
+
save_graph_cache,
|
|
56
|
+
select_local_force_subgraph,
|
|
57
|
+
select_path_induced_subgraph,
|
|
58
|
+
to_graphviz,
|
|
59
|
+
to_mermaid,
|
|
60
|
+
to_networkx,
|
|
61
|
+
trace_dynamic_refs,
|
|
62
|
+
try_load_graph_cache,
|
|
63
|
+
validate_graph,
|
|
64
|
+
write_lightweight_viz_data,
|
|
65
|
+
write_lightweight_viz_html,
|
|
66
|
+
write_web_viz_html,
|
|
67
|
+
)
|
|
68
|
+
from .series_bindings import (
|
|
69
|
+
SeriesBindingsLoadError,
|
|
70
|
+
SeriesBindingsSchemaError,
|
|
71
|
+
ValidationReport,
|
|
72
|
+
bindings_canonical_sha256,
|
|
73
|
+
load_series_bindings,
|
|
74
|
+
resolve_series_bindings,
|
|
75
|
+
validate_series_bindings,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
__all__ = [
|
|
79
|
+
"exporter",
|
|
80
|
+
"grapher",
|
|
81
|
+
"series_bindings",
|
|
82
|
+
"GreaterThanCell",
|
|
83
|
+
"NotEqualCell",
|
|
84
|
+
"RealBetween",
|
|
85
|
+
"RealIntervalDomain",
|
|
86
|
+
"And",
|
|
87
|
+
"GuardCellRef",
|
|
88
|
+
"Compare",
|
|
89
|
+
"CycleError",
|
|
90
|
+
"CycleReport",
|
|
91
|
+
"CodeGenerator",
|
|
92
|
+
"DependencyGraph",
|
|
93
|
+
"GRAPH_CACHE_SCHEMA_VERSION",
|
|
94
|
+
"CacheValidationPolicy",
|
|
95
|
+
"DynamicRefConfig",
|
|
96
|
+
"DynamicRefError",
|
|
97
|
+
"DynamicRefLimits",
|
|
98
|
+
"DynamicRefTraceEvent",
|
|
99
|
+
"DynamicRefTraceFn",
|
|
100
|
+
"FromWorkbook",
|
|
101
|
+
"trace_dynamic_refs",
|
|
102
|
+
"Literal",
|
|
103
|
+
"Node",
|
|
104
|
+
"NodeKey",
|
|
105
|
+
"Not",
|
|
106
|
+
"NodeHook",
|
|
107
|
+
"Or",
|
|
108
|
+
"GuardExpr",
|
|
109
|
+
"ValidationResult",
|
|
110
|
+
"WorkbookCalcSettings",
|
|
111
|
+
"create_dependency_graph",
|
|
112
|
+
"normalize_blank_range_specs",
|
|
113
|
+
"list_dynamic_ref_constraint_candidates",
|
|
114
|
+
"build_graph_cache_meta",
|
|
115
|
+
"build_graph_cache_meta_portable",
|
|
116
|
+
"format_cell_key",
|
|
117
|
+
"format_key",
|
|
118
|
+
"get_calc_settings",
|
|
119
|
+
"needs_quoting",
|
|
120
|
+
"LocalForceSubgraph",
|
|
121
|
+
"LightweightVizPayload",
|
|
122
|
+
"select_local_force_subgraph",
|
|
123
|
+
"select_path_induced_subgraph",
|
|
124
|
+
"to_graphviz",
|
|
125
|
+
"to_web_viz_payload",
|
|
126
|
+
"to_mermaid",
|
|
127
|
+
"to_networkx",
|
|
128
|
+
"write_lightweight_viz_data",
|
|
129
|
+
"write_lightweight_viz_html",
|
|
130
|
+
"write_web_viz_html",
|
|
131
|
+
"validate_graph",
|
|
132
|
+
"save_graph_cache",
|
|
133
|
+
"try_load_graph_cache",
|
|
134
|
+
"CellValue",
|
|
135
|
+
"ExcelRange",
|
|
136
|
+
"FormulaEvaluator",
|
|
137
|
+
"ParseError",
|
|
138
|
+
"XlError",
|
|
139
|
+
"SeriesBindingsLoadError",
|
|
140
|
+
"SeriesBindingsSchemaError",
|
|
141
|
+
"ValidationReport",
|
|
142
|
+
"bindings_canonical_sha256",
|
|
143
|
+
"load_series_bindings",
|
|
144
|
+
"resolve_series_bindings",
|
|
145
|
+
"validate_series_bindings",
|
|
146
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Shared Excel semantics (types, coercions, operators).
|
|
2
|
+
|
|
3
|
+
Representation-agnostic types and logic used by both the evaluator runtime
|
|
4
|
+
and the standalone export runtime.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .address_keys import NormalizedAddress
|
|
8
|
+
from .coercions import (
|
|
9
|
+
excel_casefold,
|
|
10
|
+
flatten,
|
|
11
|
+
get_error,
|
|
12
|
+
numeric_values,
|
|
13
|
+
to_bool,
|
|
14
|
+
to_int,
|
|
15
|
+
to_native,
|
|
16
|
+
to_number,
|
|
17
|
+
to_string,
|
|
18
|
+
)
|
|
19
|
+
from .operators import (
|
|
20
|
+
_xl_compare,
|
|
21
|
+
xl_add,
|
|
22
|
+
xl_concat,
|
|
23
|
+
xl_div,
|
|
24
|
+
xl_eq,
|
|
25
|
+
xl_ge,
|
|
26
|
+
xl_gt,
|
|
27
|
+
xl_iferror,
|
|
28
|
+
xl_le,
|
|
29
|
+
xl_lt,
|
|
30
|
+
xl_mul,
|
|
31
|
+
xl_ne,
|
|
32
|
+
xl_neg,
|
|
33
|
+
xl_percent,
|
|
34
|
+
xl_pos,
|
|
35
|
+
xl_pow,
|
|
36
|
+
xl_sub,
|
|
37
|
+
)
|
|
38
|
+
from .types import CellValue, ExcelRange, XlError
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"NormalizedAddress",
|
|
42
|
+
"CellValue",
|
|
43
|
+
"ExcelRange",
|
|
44
|
+
"XlError",
|
|
45
|
+
"excel_casefold",
|
|
46
|
+
"flatten",
|
|
47
|
+
"get_error",
|
|
48
|
+
"numeric_values",
|
|
49
|
+
"to_bool",
|
|
50
|
+
"to_int",
|
|
51
|
+
"to_native",
|
|
52
|
+
"to_number",
|
|
53
|
+
"to_string",
|
|
54
|
+
"_xl_compare",
|
|
55
|
+
"xl_add",
|
|
56
|
+
"xl_concat",
|
|
57
|
+
"xl_div",
|
|
58
|
+
"xl_eq",
|
|
59
|
+
"xl_ge",
|
|
60
|
+
"xl_gt",
|
|
61
|
+
"xl_iferror",
|
|
62
|
+
"xl_le",
|
|
63
|
+
"xl_lt",
|
|
64
|
+
"xl_mul",
|
|
65
|
+
"xl_ne",
|
|
66
|
+
"xl_neg",
|
|
67
|
+
"xl_percent",
|
|
68
|
+
"xl_pos",
|
|
69
|
+
"xl_pow",
|
|
70
|
+
"xl_sub",
|
|
71
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Sheet-qualified address parsing and normalization helpers.
|
|
2
|
+
|
|
3
|
+
These helpers are the single source of truth for translating between the
|
|
4
|
+
external address strings that Excel users write (e.g. `'Sheet1!A1'`,
|
|
5
|
+
`"'My Sheet'!B2"`) and the canonical :data:`NormalizedAddress` form stored in the
|
|
6
|
+
`DependencyGraph` and emitted by generated code.
|
|
7
|
+
|
|
8
|
+
They live in `excel_grapher.core` so that both the grapher and the
|
|
9
|
+
evaluator/exporter can import them without violating layering rules.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
15
|
+
from typing import TypeAlias
|
|
16
|
+
|
|
17
|
+
from fastpyxl.utils.cell import column_index_from_string, coordinate_from_string
|
|
18
|
+
|
|
19
|
+
# Canonical sheet-qualified cell (`Sheet1!B1`) or range (`Sheet1!C4:Sheet1!D4`).
|
|
20
|
+
NormalizedAddress: TypeAlias = str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def needs_quoting(sheet: str) -> bool:
|
|
24
|
+
"""Return True if a sheet name must be wrapped in single quotes in a formula."""
|
|
25
|
+
return " " in sheet or "-" in sheet or "'" in sheet
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _escape_sheet_for_formula(sheet: str) -> str:
|
|
29
|
+
"""Escape apostrophes for use inside quoted sheet names."""
|
|
30
|
+
return sheet.replace("'", "''")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def quote_sheet_if_needed(sheet: str) -> str:
|
|
34
|
+
"""Return a sheet name quoted for formulas when quoting is required."""
|
|
35
|
+
if not needs_quoting(sheet):
|
|
36
|
+
return sheet
|
|
37
|
+
return "'" + _escape_sheet_for_formula(sheet) + "'"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_address(address: str) -> tuple[str, str]:
|
|
41
|
+
"""Parse a sheet-qualified address into `(sheet, cell_coord)`.
|
|
42
|
+
|
|
43
|
+
The returned sheet name has any surrounding single quotes stripped and any
|
|
44
|
+
escaped apostrophes (`''`) unescaped to a single apostrophe.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
>>> parse_address("Sheet1!A1")
|
|
48
|
+
('Sheet1', 'A1')
|
|
49
|
+
>>> parse_address("'My Sheet'!B2")
|
|
50
|
+
('My Sheet', 'B2')
|
|
51
|
+
>>> parse_address("'It''s Data'!C3")
|
|
52
|
+
("It's Data", 'C3')
|
|
53
|
+
"""
|
|
54
|
+
if address.startswith("'"):
|
|
55
|
+
i = 1
|
|
56
|
+
while i < len(address):
|
|
57
|
+
if address[i] == "'":
|
|
58
|
+
if i + 1 < len(address) and address[i + 1] == "'":
|
|
59
|
+
i += 2
|
|
60
|
+
continue
|
|
61
|
+
break
|
|
62
|
+
i += 1
|
|
63
|
+
sheet = address[1:i].replace("''", "'")
|
|
64
|
+
rest = address[i + 1 :]
|
|
65
|
+
if rest.startswith("!"):
|
|
66
|
+
return sheet, rest[1:]
|
|
67
|
+
raise ValueError(f"Invalid address format: {address}")
|
|
68
|
+
|
|
69
|
+
if "!" in address:
|
|
70
|
+
sheet, cell = address.rsplit("!", 1)
|
|
71
|
+
return sheet, cell
|
|
72
|
+
|
|
73
|
+
raise ValueError(f"Address must be sheet-qualified: {address}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def format_key(sheet: str, cell: str) -> NormalizedAddress:
|
|
77
|
+
"""Format a sheet and A1 cell coordinate into a canonical address string."""
|
|
78
|
+
return f"{quote_sheet_if_needed(sheet)}!{cell}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def format_cell_key(sheet: str, column: str, row: int) -> NormalizedAddress:
|
|
82
|
+
"""Format a (sheet, column_letters, row) triple into a canonical address."""
|
|
83
|
+
return f"{quote_sheet_if_needed(sheet)}!{column}{row}"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def normalize_key(key: str) -> NormalizedAddress:
|
|
87
|
+
"""Normalize an address to canonical :data:`NormalizedAddress` form.
|
|
88
|
+
|
|
89
|
+
Unnecessary quoting is stripped; sheet names with spaces, hyphens, or
|
|
90
|
+
apostrophes are quoted. For single cells, the result matches `Node.key`.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
>>> normalize_key("'Sheet1'!A1")
|
|
94
|
+
'Sheet1!A1'
|
|
95
|
+
>>> normalize_key("'My Sheet'!B2")
|
|
96
|
+
"'My Sheet'!B2"
|
|
97
|
+
"""
|
|
98
|
+
sheet, cell = parse_address(key)
|
|
99
|
+
return format_key(sheet, cell)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def make_node_key_sort_key(
|
|
103
|
+
sheet_order: Sequence[str],
|
|
104
|
+
) -> Callable[[NormalizedAddress], tuple[int, str, int, int]]:
|
|
105
|
+
"""Build a key function for workbook-aligned `NodeKey` sorting.
|
|
106
|
+
|
|
107
|
+
Keys are ordered by:
|
|
108
|
+
1) workbook sheet order (from `sheet_order`),
|
|
109
|
+
2) row number,
|
|
110
|
+
3) column number.
|
|
111
|
+
|
|
112
|
+
Sheets not present in `sheet_order` are placed after known sheets and
|
|
113
|
+
sorted by sheet name.
|
|
114
|
+
"""
|
|
115
|
+
sheet_rank = {name: idx for idx, name in enumerate(sheet_order)}
|
|
116
|
+
fallback_rank = len(sheet_rank)
|
|
117
|
+
|
|
118
|
+
def _sort_key(node_key: NormalizedAddress) -> tuple[int, str, int, int]:
|
|
119
|
+
sheet, cell = parse_address(node_key)
|
|
120
|
+
col_letters, row = coordinate_from_string(cell.replace("$", ""))
|
|
121
|
+
col = int(column_index_from_string(col_letters))
|
|
122
|
+
return (sheet_rank.get(sheet, fallback_rank), sheet, int(row), col)
|
|
123
|
+
|
|
124
|
+
return _sort_key
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def sort_node_keys(
|
|
128
|
+
node_keys: Iterable[NormalizedAddress], *, sheet_order: Sequence[str]
|
|
129
|
+
) -> list[NormalizedAddress]:
|
|
130
|
+
"""Return `node_keys` sorted by workbook sheet order, then row, then column."""
|
|
131
|
+
return sorted(node_keys, key=make_node_key_sort_key(sheet_order))
|