python-calamine-reducto 0.7.6__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.
- python_calamine_reducto-0.7.6/.github/workflows/CI.yml +67 -0
- python_calamine_reducto-0.7.6/.gitignore +141 -0
- python_calamine_reducto-0.7.6/.pre-commit-config.yaml +64 -0
- python_calamine_reducto-0.7.6/.python-version +1 -0
- python_calamine_reducto-0.7.6/CLAUDE.md +131 -0
- python_calamine_reducto-0.7.6/Cargo.lock +1091 -0
- python_calamine_reducto-0.7.6/Cargo.toml +23 -0
- python_calamine_reducto-0.7.6/LICENSE +21 -0
- python_calamine_reducto-0.7.6/PKG-INFO +15 -0
- python_calamine_reducto-0.7.6/README.md +158 -0
- python_calamine_reducto-0.7.6/build.rs +3 -0
- python_calamine_reducto-0.7.6/pyproject.toml +52 -0
- python_calamine_reducto-0.7.6/python/python_calamine/__init__.py +43 -0
- python_calamine_reducto-0.7.6/python/python_calamine/_python_calamine.pyi +316 -0
- python_calamine_reducto-0.7.6/python/python_calamine/py.typed +0 -0
- python_calamine_reducto-0.7.6/renovate.json +6 -0
- python_calamine_reducto-0.7.6/src/lib.rs +44 -0
- python_calamine_reducto-0.7.6/src/types/cell.rs +133 -0
- python_calamine_reducto-0.7.6/src/types/errors.rs +78 -0
- python_calamine_reducto-0.7.6/src/types/grid.rs +1745 -0
- python_calamine_reducto-0.7.6/src/types/mod.rs +15 -0
- python_calamine_reducto-0.7.6/src/types/sheet.rs +282 -0
- python_calamine_reducto-0.7.6/src/types/table.rs +71 -0
- python_calamine_reducto-0.7.6/src/types/workbook.rs +1097 -0
- python_calamine_reducto-0.7.6/tests/__init__.py +0 -0
- python_calamine_reducto-0.7.6/tests/conftest.py +73 -0
- python_calamine_reducto-0.7.6/tests/create_empty_merge_fixture.py +34 -0
- python_calamine_reducto-0.7.6/tests/create_richtext_fixture.py +126 -0
- python_calamine_reducto-0.7.6/tests/create_test_fixture.py +90 -0
- python_calamine_reducto-0.7.6/tests/data/any_sheets.ods +0 -0
- python_calamine_reducto-0.7.6/tests/data/any_sheets.xls +0 -0
- python_calamine_reducto-0.7.6/tests/data/any_sheets.xlsb +0 -0
- python_calamine_reducto-0.7.6/tests/data/any_sheets.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/base.ods +0 -0
- python_calamine_reducto-0.7.6/tests/data/base.xls +0 -0
- python_calamine_reducto-0.7.6/tests/data/base.xlsb +0 -0
- python_calamine_reducto-0.7.6/tests/data/base.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/dropdown_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/empty_file.ods +0 -0
- python_calamine_reducto-0.7.6/tests/data/empty_file.xlsb +0 -0
- python_calamine_reducto-0.7.6/tests/data/empty_file.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/empty_merge_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/formula_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/grid_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/image_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/issue139.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/multi_image_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/password.ods +0 -0
- python_calamine_reducto-0.7.6/tests/data/password.xls +0 -0
- python_calamine_reducto-0.7.6/tests/data/password.xlsb +0 -0
- python_calamine_reducto-0.7.6/tests/data/password.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/richtext_test.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/data/table-multiple.xlsx +0 -0
- python_calamine_reducto-0.7.6/tests/test_base.py +384 -0
- python_calamine_reducto-0.7.6/tests/test_grid.py +1093 -0
- python_calamine_reducto-0.7.6/tests/test_images.py +128 -0
- python_calamine_reducto-0.7.6/tests/test_pandas.py +112 -0
- python_calamine_reducto-0.7.6/tests/test_sheet_type.py +120 -0
- python_calamine_reducto-0.7.6/tests/test_tables.py +73 -0
- python_calamine_reducto-0.7.6/uv.lock +1065 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request: {}
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: test ${{ matrix.python-version }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version:
|
|
16
|
+
- "3.11"
|
|
17
|
+
- "3.12"
|
|
18
|
+
- "3.13"
|
|
19
|
+
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
env:
|
|
23
|
+
PYTHON: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v6
|
|
27
|
+
|
|
28
|
+
- name: configure git for private deps
|
|
29
|
+
run: git config --global url."https://x-access-token:${{ secrets.GH_PAT }}@github.com/".insteadOf "https://github.com/"
|
|
30
|
+
|
|
31
|
+
- name: install rust stable
|
|
32
|
+
uses: dtolnay/rust-toolchain@master
|
|
33
|
+
with:
|
|
34
|
+
toolchain: stable
|
|
35
|
+
|
|
36
|
+
- id: cache-rust
|
|
37
|
+
name: cache rust
|
|
38
|
+
uses: Swatinem/rust-cache@v2
|
|
39
|
+
with:
|
|
40
|
+
key: v3
|
|
41
|
+
|
|
42
|
+
- name: set up python
|
|
43
|
+
uses: actions/setup-python@v6
|
|
44
|
+
with:
|
|
45
|
+
python-version: ${{ matrix.python-version }}
|
|
46
|
+
|
|
47
|
+
- run: pip install pytest numpy
|
|
48
|
+
|
|
49
|
+
- run: pip install -e .
|
|
50
|
+
env:
|
|
51
|
+
RUST_BACKTRACE: 1
|
|
52
|
+
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
|
53
|
+
|
|
54
|
+
- run: pytest
|
|
55
|
+
|
|
56
|
+
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
|
|
57
|
+
check:
|
|
58
|
+
if: always()
|
|
59
|
+
needs: [test]
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- name: Decide whether the needed jobs succeeded or failed
|
|
63
|
+
uses: re-actors/alls-green@release/v1
|
|
64
|
+
with:
|
|
65
|
+
jobs: ${{ toJSON(needs) }}
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Generated by Cargo
|
|
2
|
+
# will have compiled files and executables
|
|
3
|
+
debug/
|
|
4
|
+
target/
|
|
5
|
+
|
|
6
|
+
# These are backup files generated by rustfmt
|
|
7
|
+
**/*.rs.bk
|
|
8
|
+
|
|
9
|
+
# Initial .gitignore from GitHub for Python project
|
|
10
|
+
# Byte-compiled / optimized / DLL files
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
*$py.class
|
|
14
|
+
|
|
15
|
+
# C extensions
|
|
16
|
+
*.so
|
|
17
|
+
|
|
18
|
+
# Distribution / packaging
|
|
19
|
+
.Python
|
|
20
|
+
build/
|
|
21
|
+
develop-eggs/
|
|
22
|
+
dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib/
|
|
27
|
+
lib64/
|
|
28
|
+
parts/
|
|
29
|
+
sdist/
|
|
30
|
+
var/
|
|
31
|
+
wheels/
|
|
32
|
+
pip-wheel-metadata/
|
|
33
|
+
share/python-wheels/
|
|
34
|
+
*.egg-info/
|
|
35
|
+
.installed.cfg
|
|
36
|
+
*.egg
|
|
37
|
+
MANIFEST
|
|
38
|
+
|
|
39
|
+
# PyInstaller
|
|
40
|
+
# Usually these files are written by a python script from a template
|
|
41
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
42
|
+
*.manifest
|
|
43
|
+
*.spec
|
|
44
|
+
|
|
45
|
+
# Installer logs
|
|
46
|
+
pip-log.txt
|
|
47
|
+
pip-delete-this-directory.txt
|
|
48
|
+
|
|
49
|
+
# Unit test / coverage reports
|
|
50
|
+
htmlcov/
|
|
51
|
+
.tox/
|
|
52
|
+
.nox/
|
|
53
|
+
.coverage
|
|
54
|
+
.coverage.*
|
|
55
|
+
.cache
|
|
56
|
+
nosetests.xml
|
|
57
|
+
coverage.xml
|
|
58
|
+
*.cover
|
|
59
|
+
*.py,cover
|
|
60
|
+
.hypothesis/
|
|
61
|
+
.pytest_cache/
|
|
62
|
+
|
|
63
|
+
# Translations
|
|
64
|
+
*.mo
|
|
65
|
+
*.pot
|
|
66
|
+
|
|
67
|
+
# Django stuff:
|
|
68
|
+
*.log
|
|
69
|
+
local_settings.py
|
|
70
|
+
db.sqlite3
|
|
71
|
+
db.sqlite3-journal
|
|
72
|
+
|
|
73
|
+
# Flask stuff:
|
|
74
|
+
instance/
|
|
75
|
+
.webassets-cache
|
|
76
|
+
|
|
77
|
+
# Scrapy stuff:
|
|
78
|
+
.scrapy
|
|
79
|
+
|
|
80
|
+
# Sphinx documentation
|
|
81
|
+
docs/_build/
|
|
82
|
+
|
|
83
|
+
# PyBuilder
|
|
84
|
+
target/
|
|
85
|
+
|
|
86
|
+
# Jupyter Notebook
|
|
87
|
+
.ipynb_checkpoints
|
|
88
|
+
|
|
89
|
+
# IPython
|
|
90
|
+
profile_default/
|
|
91
|
+
ipython_config.py
|
|
92
|
+
|
|
93
|
+
# pyenv
|
|
94
|
+
.python-version
|
|
95
|
+
|
|
96
|
+
# pipenv
|
|
97
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
98
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
99
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
100
|
+
# install all needed dependencies.
|
|
101
|
+
#Pipfile.lock
|
|
102
|
+
|
|
103
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
104
|
+
__pypackages__/
|
|
105
|
+
|
|
106
|
+
# Celery stuff
|
|
107
|
+
celerybeat-schedule
|
|
108
|
+
celerybeat.pid
|
|
109
|
+
|
|
110
|
+
# SageMath parsed files
|
|
111
|
+
*.sage.py
|
|
112
|
+
|
|
113
|
+
# Environments
|
|
114
|
+
.env
|
|
115
|
+
.venv
|
|
116
|
+
env/
|
|
117
|
+
venv/
|
|
118
|
+
ENV/
|
|
119
|
+
env.bak/
|
|
120
|
+
venv.bak/
|
|
121
|
+
|
|
122
|
+
# Spyder project settings
|
|
123
|
+
.spyderproject
|
|
124
|
+
.spyproject
|
|
125
|
+
|
|
126
|
+
# Rope project settings
|
|
127
|
+
.ropeproject
|
|
128
|
+
|
|
129
|
+
# mkdocs documentation
|
|
130
|
+
/site
|
|
131
|
+
|
|
132
|
+
# mypy
|
|
133
|
+
.mypy_cache/
|
|
134
|
+
.dmypy.json
|
|
135
|
+
dmypy.json
|
|
136
|
+
|
|
137
|
+
# Pyre type checker
|
|
138
|
+
.pyre/
|
|
139
|
+
|
|
140
|
+
# IDE stuff
|
|
141
|
+
.vscode
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.5.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-added-large-files
|
|
6
|
+
- id: check-toml
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
args:
|
|
9
|
+
- --unsafe
|
|
10
|
+
- id: end-of-file-fixer
|
|
11
|
+
- id: trailing-whitespace
|
|
12
|
+
- repo: https://github.com/asottile/pyupgrade
|
|
13
|
+
rev: v3.15.0
|
|
14
|
+
hooks:
|
|
15
|
+
- id: pyupgrade
|
|
16
|
+
args:
|
|
17
|
+
- --py3-plus
|
|
18
|
+
- --keep-runtime-typing
|
|
19
|
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
20
|
+
rev: v0.2.1
|
|
21
|
+
hooks:
|
|
22
|
+
- id: ruff
|
|
23
|
+
args:
|
|
24
|
+
- --fix
|
|
25
|
+
- repo: https://github.com/pycqa/isort
|
|
26
|
+
rev: 5.13.2
|
|
27
|
+
hooks:
|
|
28
|
+
- id: isort
|
|
29
|
+
name: isort (python)
|
|
30
|
+
- id: isort
|
|
31
|
+
name: isort (pyi)
|
|
32
|
+
types: [pyi]
|
|
33
|
+
- repo: https://github.com/psf/black
|
|
34
|
+
rev: 24.1.1
|
|
35
|
+
hooks:
|
|
36
|
+
- id: black
|
|
37
|
+
- repo: local
|
|
38
|
+
hooks:
|
|
39
|
+
- id: mypy
|
|
40
|
+
name: mypy
|
|
41
|
+
entry: mypy
|
|
42
|
+
language: python
|
|
43
|
+
pass_filenames: false
|
|
44
|
+
- id: mypy-stubtest
|
|
45
|
+
name: mypy-stubtest
|
|
46
|
+
entry: stubtest
|
|
47
|
+
args:
|
|
48
|
+
- python_calamine
|
|
49
|
+
language: python
|
|
50
|
+
pass_filenames: false
|
|
51
|
+
- repo: local
|
|
52
|
+
hooks:
|
|
53
|
+
- id: rust-linting
|
|
54
|
+
name: Rust linting
|
|
55
|
+
entry: cargo fmt --all -- --config group_imports=StdExternalCrate,imports_granularity=Module
|
|
56
|
+
pass_filenames: true
|
|
57
|
+
types: [rust]
|
|
58
|
+
language: system
|
|
59
|
+
- id: rust-clippy
|
|
60
|
+
name: Rust clippy
|
|
61
|
+
entry: cargo clippy -- -D warnings -A incomplete_features -W clippy::dbg_macro -W clippy::print_stdout
|
|
62
|
+
pass_filenames: false
|
|
63
|
+
types: [rust]
|
|
64
|
+
language: system
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Reducto python-calamine Fork
|
|
2
|
+
|
|
3
|
+
Private fork of [dimastbk/python-calamine](https://github.com/dimastbk/python-calamine) — PyO3 bindings for the calamine Rust spreadsheet reader.
|
|
4
|
+
|
|
5
|
+
## What This Repo Is
|
|
6
|
+
|
|
7
|
+
Python bindings for calamine, extended with Reducto's `CellGrid` type and workbook style/layout/bounds APIs. Used by `api4/src/sheets/` as a 40x faster replacement for openpyxl.
|
|
8
|
+
|
|
9
|
+
**Dependency chain:** `api4` → `python-calamine-reducto` (PyPI) → `reductoai/calamine` + `reductoai/ssfmt` (Rust crates, compiled into the wheel)
|
|
10
|
+
|
|
11
|
+
## Building
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Create venv and install
|
|
15
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
16
|
+
maturin develop # debug build (~4s incremental)
|
|
17
|
+
maturin develop --release # release build (~30s clean)
|
|
18
|
+
|
|
19
|
+
# Or build into api4's venv directly:
|
|
20
|
+
source /path/to/api4/.venv/bin/activate
|
|
21
|
+
maturin develop --release
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Rust toolchain (`rustup`). The Cargo.toml pulls `reductoai/calamine` and `reductoai/ssfmt` from git, which are private — you need git auth configured:
|
|
25
|
+
```bash
|
|
26
|
+
git config --global url."https://x-access-token:YOUR_PAT@github.com/".insteadOf "https://github.com/"
|
|
27
|
+
# OR just have SSH access to reductoai org
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Testing
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install pytest numpy
|
|
34
|
+
pytest # all tests
|
|
35
|
+
pytest tests/test_grid.py # CellGrid tests only
|
|
36
|
+
pytest tests/test_base.py # upstream tests only
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The test fixture `tests/data/grid_test.xlsx` is generated by `tests/create_test_fixture.py` (requires openpyxl). Regenerate if you change test expectations:
|
|
40
|
+
```bash
|
|
41
|
+
pip install openpyxl
|
|
42
|
+
python tests/create_test_fixture.py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Publishing to PyPI
|
|
46
|
+
|
|
47
|
+
**Package name convention:** Reducto forks are published as `{package}-reducto` on PyPI. This package is `python-calamine-reducto`. The Python import stays `python_calamine`.
|
|
48
|
+
|
|
49
|
+
**Steps to publish a new version:**
|
|
50
|
+
|
|
51
|
+
1. Update version in `Cargo.toml` (e.g., `version = "0.7.1"`)
|
|
52
|
+
2. Commit, merge to `main`, and tag:
|
|
53
|
+
```bash
|
|
54
|
+
git tag v0.7.1
|
|
55
|
+
git push origin v0.7.1
|
|
56
|
+
```
|
|
57
|
+
3. Build wheels for each target platform:
|
|
58
|
+
```bash
|
|
59
|
+
# macOS ARM (native):
|
|
60
|
+
maturin build --release --interpreter 3.11 3.12 3.13
|
|
61
|
+
|
|
62
|
+
# manylinux x86_64 (from macOS, via Docker):
|
|
63
|
+
docker run --rm --platform linux/amd64 \
|
|
64
|
+
--entrypoint /bin/bash \
|
|
65
|
+
-v $(pwd):/io \
|
|
66
|
+
-v $HOME/.ssh:/root/.ssh:ro \
|
|
67
|
+
-e CARGO_NET_GIT_FETCH_WITH_CLI=true \
|
|
68
|
+
-e GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" \
|
|
69
|
+
ghcr.io/pyo3/maturin -c '
|
|
70
|
+
git config --global url."git@github.com:".insteadOf "https://github.com/"
|
|
71
|
+
maturin build --release --interpreter 3.11 3.12 3.13
|
|
72
|
+
'
|
|
73
|
+
```
|
|
74
|
+
Wheels go into `target/wheels/`.
|
|
75
|
+
|
|
76
|
+
**Note:** The Docker command mounts your SSH keys for private repo access. Ensure Docker Desktop is running and you have SSH access to `reductoai` org repos.
|
|
77
|
+
|
|
78
|
+
4. Upload to PyPI:
|
|
79
|
+
```bash
|
|
80
|
+
pip install twine
|
|
81
|
+
twine upload target/wheels/*
|
|
82
|
+
```
|
|
83
|
+
Uses token from `~/.pypirc`. If not configured:
|
|
84
|
+
```ini
|
|
85
|
+
# ~/.pypirc
|
|
86
|
+
[pypi]
|
|
87
|
+
username = __token__
|
|
88
|
+
password = pypi-...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
5. Update api4's `pyproject.toml`:
|
|
92
|
+
```
|
|
93
|
+
"python-calamine-reducto==0.7.1"
|
|
94
|
+
```
|
|
95
|
+
Then run `uv lock`.
|
|
96
|
+
|
|
97
|
+
**Note:** Because this package has private git dependencies (`reductoai/calamine`, `reductoai/ssfmt`), you need git auth configured when building. The `GH_PAT` or SSH access must be available during `maturin build`.
|
|
98
|
+
|
|
99
|
+
## Key Files
|
|
100
|
+
|
|
101
|
+
| Path | What |
|
|
102
|
+
|------|------|
|
|
103
|
+
| `src/types/grid.rs` | CellGrid, CellProxy, CellGridView — the core Reducto addition (~1500 lines) |
|
|
104
|
+
| `src/types/workbook.rs` | get_sheet_grid, get_sheet_styles, get_sheet_layout, get_sheet_bounds |
|
|
105
|
+
| `src/types/mod.rs` | Module exports |
|
|
106
|
+
| `src/lib.rs` | PyO3 module registration |
|
|
107
|
+
| `python/python_calamine/__init__.py` | Python re-exports |
|
|
108
|
+
| `Cargo.toml` | Points calamine + ssfmt deps to reductoai forks |
|
|
109
|
+
| `tests/test_grid.py` | 86 tests for all CellGrid APIs |
|
|
110
|
+
|
|
111
|
+
## Architecture
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Python code
|
|
115
|
+
→ python_calamine (PyO3 module)
|
|
116
|
+
→ CellGrid (src/types/grid.rs) — 2D grid with per-cell value/style/formatting
|
|
117
|
+
→ CalamineWorkbook (src/types/workbook.rs) — xlsx/xls/ods reader
|
|
118
|
+
→ calamine (Rust crate, from reductoai/calamine) — parsing + styles
|
|
119
|
+
→ ssfmt (Rust crate, from reductoai/ssfmt) — ECMA-376 number formatting
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**CellGrid fields per cell:** value, formatted_value, font_bold, font_size, number_format, cell_textcolor, cell_highlight, cell_formula, header, is_merged, merge_parent, coord
|
|
123
|
+
|
|
124
|
+
## Making Changes
|
|
125
|
+
|
|
126
|
+
1. Branch from `main`
|
|
127
|
+
2. Edit Rust code, rebuild with `maturin develop`
|
|
128
|
+
3. Run `pytest` to verify
|
|
129
|
+
4. Push, open PR against `main`
|
|
130
|
+
|
|
131
|
+
If you changed something in `reductoai/calamine` or `reductoai/ssfmt`, merge those PRs first — this repo's `Cargo.toml` tracks `branch = "main"` for both.
|