xlsxturbo 0.9.0__tar.gz → 0.10.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.
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/CHANGELOG.md +44 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/Cargo.lock +5 -5
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/Cargo.toml +1 -1
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/PKG-INFO +227 -14
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/README.md +226 -13
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/ROADMAP.md +15 -19
- xlsxturbo-0.10.1/benchmarks/benchmark.py +652 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/pyproject.toml +1 -1
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/python/xlsxturbo/__init__.pyi +71 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/src/lib.rs +672 -4
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/tests/test_features.py +364 -0
- xlsxturbo-0.9.0/benchmark.py +0 -237
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/.github/dependabot.yml +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/.github/workflows/ci.yml +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/.github/workflows/release.yml +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/.gitignore +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/BUILD.md +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/LICENSE +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1/benchmarks}/benchmark_parallel.py +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/python/xlsxturbo/__init__.py +0 -0
- {xlsxturbo-0.9.0 → xlsxturbo-0.10.1}/src/main.rs +0 -0
|
@@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.10.1] - 2026-01-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Benchmark suite reorganization** - Moved benchmarks to `benchmarks/` directory
|
|
12
|
+
- New `benchmarks/benchmark.py` - comprehensive comparison vs polars, pandas+openpyxl, pandas+xlsxwriter
|
|
13
|
+
- Moved `benchmark_parallel.py` to `benchmarks/`
|
|
14
|
+
- Removed obsolete `benchmark.py` (referenced old Rust binary)
|
|
15
|
+
- **README Performance section** - Updated with reproducible benchmark methodology
|
|
16
|
+
- Changed performance claim from "~25x faster" to "~6x faster" (accurate for typical workloads)
|
|
17
|
+
- Added disclaimer that results vary by system
|
|
18
|
+
- Linked to Benchmarking section for running your own tests
|
|
19
|
+
|
|
20
|
+
## [0.10.0] - 2026-01-16
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **Comments/Notes** - Add cell annotations with optional author
|
|
24
|
+
- Simple text: `comments={'A1': 'Note text'}`
|
|
25
|
+
- With author: `comments={'A1': {'text': 'Note', 'author': 'John'}}`
|
|
26
|
+
- Available in both `df_to_xlsx()` and `dfs_to_xlsx()` with per-sheet overrides
|
|
27
|
+
- **Data Validation** - Add dropdowns and constraints to columns
|
|
28
|
+
- List (dropdown): `validations={'Status': {'type': 'list', 'values': ['Open', 'Closed']}}`
|
|
29
|
+
- Whole number: `validations={'Score': {'type': 'whole_number', 'min': 0, 'max': 100}}`
|
|
30
|
+
- Decimal: `validations={'Price': {'type': 'decimal', 'min': 0.0, 'max': 999.99}}`
|
|
31
|
+
- Text length: `validations={'Code': {'type': 'text_length', 'min': 3, 'max': 10}}`
|
|
32
|
+
- Supports input/error messages: `input_title`, `input_message`, `error_title`, `error_message`
|
|
33
|
+
- Supports column patterns (like `column_formats`)
|
|
34
|
+
- Available in both `df_to_xlsx()` and `dfs_to_xlsx()` with per-sheet overrides
|
|
35
|
+
- **Rich Text** - Multiple formats within a single cell
|
|
36
|
+
- Format segments: `rich_text={'A1': [('Bold', {'bold': True}), ' normal text']}`
|
|
37
|
+
- Supports: `bold`, `italic`, `font_color`, `bg_color`, `font_size`, `underline`
|
|
38
|
+
- Mix formatted and plain text segments
|
|
39
|
+
- Available in both `df_to_xlsx()` and `dfs_to_xlsx()` with per-sheet overrides
|
|
40
|
+
- **Images** - Embed PNG, JPEG, GIF, BMP images in cells
|
|
41
|
+
- Simple path: `images={'B5': 'logo.png'}`
|
|
42
|
+
- With options: `images={'B5': {'path': 'logo.png', 'scale_width': 0.5, 'scale_height': 0.5}}`
|
|
43
|
+
- Options: `path`, `scale_width`, `scale_height`, `alt_text`
|
|
44
|
+
- Available in both `df_to_xlsx()` and `dfs_to_xlsx()` with per-sheet overrides
|
|
45
|
+
|
|
46
|
+
### Notes
|
|
47
|
+
- All new features are disabled in `constant_memory` mode (they require random access)
|
|
48
|
+
- Data validation list values are limited to 255 total characters (Excel limitation)
|
|
49
|
+
|
|
8
50
|
## [0.9.0] - 2026-01-15
|
|
9
51
|
|
|
10
52
|
### Added
|
|
@@ -171,6 +213,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
171
213
|
- Support for custom sheet names
|
|
172
214
|
- Verbose mode for progress reporting
|
|
173
215
|
|
|
216
|
+
[0.10.1]: https://github.com/tstone-1/xlsxturbo/releases/tag/v0.10.1
|
|
217
|
+
[0.10.0]: https://github.com/tstone-1/xlsxturbo/releases/tag/v0.10.0
|
|
174
218
|
[0.9.0]: https://github.com/tstone-1/xlsxturbo/releases/tag/v0.9.0
|
|
175
219
|
[0.8.0]: https://github.com/tstone-1/xlsxturbo/releases/tag/v0.8.0
|
|
176
220
|
[0.7.0]: https://github.com/tstone-1/xlsxturbo/releases/tag/v0.7.0
|
|
@@ -673,9 +673,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
|
673
673
|
|
|
674
674
|
[[package]]
|
|
675
675
|
name = "wasip2"
|
|
676
|
-
version = "1.0.
|
|
676
|
+
version = "1.0.2+wasi-0.2.9"
|
|
677
677
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
678
|
-
checksum = "
|
|
678
|
+
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
|
679
679
|
dependencies = [
|
|
680
680
|
"wit-bindgen",
|
|
681
681
|
]
|
|
@@ -795,13 +795,13 @@ dependencies = [
|
|
|
795
795
|
|
|
796
796
|
[[package]]
|
|
797
797
|
name = "wit-bindgen"
|
|
798
|
-
version = "0.
|
|
798
|
+
version = "0.51.0"
|
|
799
799
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
800
|
-
checksum = "
|
|
800
|
+
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
|
801
801
|
|
|
802
802
|
[[package]]
|
|
803
803
|
name = "xlsxturbo"
|
|
804
|
-
version = "0.
|
|
804
|
+
version = "0.10.1"
|
|
805
805
|
dependencies = [
|
|
806
806
|
"chrono",
|
|
807
807
|
"clap",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xlsxturbo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -43,6 +43,10 @@ High-performance Excel writer with automatic type detection. Written in Rust, us
|
|
|
43
43
|
- **Formula columns** - add calculated columns with Excel formulas
|
|
44
44
|
- **Merged cells** - merge cell ranges for headers and titles
|
|
45
45
|
- **Hyperlinks** - add clickable links to cells
|
|
46
|
+
- **Comments/Notes** - add cell annotations with optional author
|
|
47
|
+
- **Data validation** - dropdowns, number ranges, text length constraints
|
|
48
|
+
- **Rich text** - multiple formats within a single cell
|
|
49
|
+
- **Images** - embed PNG, JPEG, GIF, BMP in cells
|
|
46
50
|
- **Auto-fit columns** - automatically adjust column widths to fit content
|
|
47
51
|
- **Custom column widths** - set specific widths per column or cap all with _all
|
|
48
52
|
- **Header styling** - bold, colors, font size for header row
|
|
@@ -60,7 +64,7 @@ High-performance Excel writer with automatic type detection. Written in Rust, us
|
|
|
60
64
|
- Datetimes (ISO 8601) → Excel datetimes
|
|
61
65
|
- `NaN`/`Inf` → Empty cells (graceful handling)
|
|
62
66
|
- Everything else → Text
|
|
63
|
-
- **~
|
|
67
|
+
- **~6x faster** than pandas + openpyxl (see [benchmarks](#performance))
|
|
64
68
|
- **Memory efficient** - streams data with 1MB buffer
|
|
65
69
|
- Available as both **Python library** and **CLI tool**
|
|
66
70
|
|
|
@@ -345,6 +349,10 @@ Available per-sheet options:
|
|
|
345
349
|
- `formula_columns` (dict): Calculated columns with Excel formulas (column name -> formula template)
|
|
346
350
|
- `merged_ranges` (list): List of (range, text) or (range, text, format) tuples to merge cells
|
|
347
351
|
- `hyperlinks` (list): List of (cell, url) or (cell, url, display_text) tuples to add clickable links
|
|
352
|
+
- `comments` (dict): Cell comments/notes (cell_ref -> text or {text, author})
|
|
353
|
+
- `validations` (dict): Data validation rules (column name/pattern -> validation config)
|
|
354
|
+
- `rich_text` (dict): Rich text with multiple formats (cell_ref -> list of segments)
|
|
355
|
+
- `images` (dict): Embedded images (cell_ref -> path or {path, scale_width, scale_height, alt_text})
|
|
348
356
|
|
|
349
357
|
### Conditional Formatting
|
|
350
358
|
|
|
@@ -515,6 +523,193 @@ xlsxturbo.df_to_xlsx(df, "companies.xlsx",
|
|
|
515
523
|
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
516
524
|
- Not available in constant memory mode
|
|
517
525
|
|
|
526
|
+
### Comments/Notes
|
|
527
|
+
|
|
528
|
+
Add cell annotations (hover to view):
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
import xlsxturbo
|
|
532
|
+
import pandas as pd
|
|
533
|
+
|
|
534
|
+
df = pd.DataFrame({
|
|
535
|
+
'product': ['Widget A', 'Widget B'],
|
|
536
|
+
'price': [19.99, 29.99]
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
xlsxturbo.df_to_xlsx(df, "report.xlsx",
|
|
540
|
+
comments={
|
|
541
|
+
# Simple text comment
|
|
542
|
+
'A1': 'This column contains product names',
|
|
543
|
+
# Comment with author
|
|
544
|
+
'B1': {'text': 'Prices in USD', 'author': 'Finance Team'}
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**Comment format:**
|
|
550
|
+
- Simple: `{'A1': 'Note text'}`
|
|
551
|
+
- With author: `{'A1': {'text': 'Note text', 'author': 'Name'}}`
|
|
552
|
+
|
|
553
|
+
**Notes:**
|
|
554
|
+
- Comments appear as small red triangles in the cell corner
|
|
555
|
+
- Hover over the cell to see the comment
|
|
556
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
557
|
+
- Not available in constant memory mode
|
|
558
|
+
|
|
559
|
+
### Data Validation
|
|
560
|
+
|
|
561
|
+
Add dropdowns and input constraints:
|
|
562
|
+
|
|
563
|
+
```python
|
|
564
|
+
import xlsxturbo
|
|
565
|
+
import pandas as pd
|
|
566
|
+
|
|
567
|
+
df = pd.DataFrame({
|
|
568
|
+
'status': ['Open', 'Closed'],
|
|
569
|
+
'score': [85, 92],
|
|
570
|
+
'price': [19.99, 29.99],
|
|
571
|
+
'code': ['ABC', 'XYZ']
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
xlsxturbo.df_to_xlsx(df, "validated.xlsx",
|
|
575
|
+
validations={
|
|
576
|
+
# Dropdown list
|
|
577
|
+
'status': {
|
|
578
|
+
'type': 'list',
|
|
579
|
+
'values': ['Open', 'Closed', 'Pending', 'Review']
|
|
580
|
+
},
|
|
581
|
+
# Whole number range (0-100)
|
|
582
|
+
'score': {
|
|
583
|
+
'type': 'whole_number',
|
|
584
|
+
'min': 0,
|
|
585
|
+
'max': 100,
|
|
586
|
+
'error_title': 'Invalid Score',
|
|
587
|
+
'error_message': 'Score must be between 0 and 100'
|
|
588
|
+
},
|
|
589
|
+
# Decimal range
|
|
590
|
+
'price': {
|
|
591
|
+
'type': 'decimal',
|
|
592
|
+
'min': 0.0,
|
|
593
|
+
'max': 999.99
|
|
594
|
+
},
|
|
595
|
+
# Text length constraint
|
|
596
|
+
'code': {
|
|
597
|
+
'type': 'text_length',
|
|
598
|
+
'min': 3,
|
|
599
|
+
'max': 10
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
)
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Validation types:**
|
|
606
|
+
|
|
607
|
+
| Type | Aliases | Description | Options |
|
|
608
|
+
|------|---------|-------------|---------|
|
|
609
|
+
| `list` | - | Dropdown menu | `values` (list of strings, max 255 chars total) |
|
|
610
|
+
| `whole_number` | `whole`, `integer` | Integer range | `min`, `max` |
|
|
611
|
+
| `decimal` | `number` | Decimal range | `min`, `max` |
|
|
612
|
+
| `text_length` | `textlength`, `length` | Character count | `min`, `max` |
|
|
613
|
+
|
|
614
|
+
**Optional message options:**
|
|
615
|
+
- `input_title`, `input_message`: Prompt shown when cell is selected
|
|
616
|
+
- `error_title`, `error_message`: Message shown when invalid data is entered
|
|
617
|
+
|
|
618
|
+
**Notes:**
|
|
619
|
+
- Validations apply to the data rows of the specified column
|
|
620
|
+
- Column patterns work: `'score_*': {...}` matches all columns starting with `score_`
|
|
621
|
+
- If only `min` or only `max` is specified, the other defaults to the type's extreme value
|
|
622
|
+
- List validation values are limited to 255 total characters (Excel limitation)
|
|
623
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
624
|
+
- Not available in constant memory mode
|
|
625
|
+
|
|
626
|
+
### Rich Text
|
|
627
|
+
|
|
628
|
+
Multiple formats within a single cell:
|
|
629
|
+
|
|
630
|
+
```python
|
|
631
|
+
import xlsxturbo
|
|
632
|
+
import pandas as pd
|
|
633
|
+
|
|
634
|
+
df = pd.DataFrame({'A': [1, 2, 3]})
|
|
635
|
+
|
|
636
|
+
xlsxturbo.df_to_xlsx(df, "rich.xlsx",
|
|
637
|
+
rich_text={
|
|
638
|
+
'D1': [
|
|
639
|
+
('Important: ', {'bold': True, 'font_color': 'red'}),
|
|
640
|
+
'Please review ',
|
|
641
|
+
('all', {'italic': True}),
|
|
642
|
+
' values'
|
|
643
|
+
],
|
|
644
|
+
'D2': [
|
|
645
|
+
('Status: ', {'bold': True}),
|
|
646
|
+
('OK', {'font_color': 'green', 'bold': True})
|
|
647
|
+
]
|
|
648
|
+
}
|
|
649
|
+
)
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Segment format:**
|
|
653
|
+
- Formatted: `('text', {'bold': True, 'font_color': 'blue'})`
|
|
654
|
+
- Plain: `'plain text'` (no formatting)
|
|
655
|
+
|
|
656
|
+
**Available format options:**
|
|
657
|
+
- `bold` (bool)
|
|
658
|
+
- `italic` (bool)
|
|
659
|
+
- `font_color` (str): '#RRGGBB' or named color
|
|
660
|
+
- `bg_color` (str): Background color
|
|
661
|
+
- `font_size` (float)
|
|
662
|
+
- `underline` (bool)
|
|
663
|
+
|
|
664
|
+
**Notes:**
|
|
665
|
+
- Rich text writes to the specified cell position (overwrites existing content)
|
|
666
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
667
|
+
- Not available in constant memory mode
|
|
668
|
+
|
|
669
|
+
### Images
|
|
670
|
+
|
|
671
|
+
Embed images in cells:
|
|
672
|
+
|
|
673
|
+
```python
|
|
674
|
+
import xlsxturbo
|
|
675
|
+
import pandas as pd
|
|
676
|
+
|
|
677
|
+
df = pd.DataFrame({'Product': ['Widget A', 'Widget B'], 'Price': [19.99, 29.99]})
|
|
678
|
+
|
|
679
|
+
xlsxturbo.df_to_xlsx(df, "catalog.xlsx",
|
|
680
|
+
autofit=True,
|
|
681
|
+
images={
|
|
682
|
+
# Simple path
|
|
683
|
+
'C2': 'images/widget_a.png',
|
|
684
|
+
# With options
|
|
685
|
+
'C3': {
|
|
686
|
+
'path': 'images/widget_b.png',
|
|
687
|
+
'scale_width': 0.5,
|
|
688
|
+
'scale_height': 0.5,
|
|
689
|
+
'alt_text': 'Widget B photo'
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
)
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Image format:**
|
|
696
|
+
- Simple: `{'C2': 'path/to/image.png'}`
|
|
697
|
+
- With options: `{'C2': {'path': '...', 'scale_width': 0.5, ...}}`
|
|
698
|
+
|
|
699
|
+
**Available options:**
|
|
700
|
+
- `path` (str, required): Path to image file
|
|
701
|
+
- `scale_width` (float): Width scale factor (1.0 = original)
|
|
702
|
+
- `scale_height` (float): Height scale factor (1.0 = original)
|
|
703
|
+
- `alt_text` (str): Alternative text for accessibility
|
|
704
|
+
|
|
705
|
+
**Supported formats:** PNG, JPEG, GIF, BMP
|
|
706
|
+
|
|
707
|
+
**Notes:**
|
|
708
|
+
- Images are positioned at the specified cell (overlays any existing content)
|
|
709
|
+
- Image file must exist; non-existent files will raise an error
|
|
710
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
711
|
+
- Not available in constant memory mode
|
|
712
|
+
|
|
518
713
|
### Constant Memory Mode (Large Files)
|
|
519
714
|
|
|
520
715
|
For very large files (millions of rows), use `constant_memory=True` to minimize RAM usage:
|
|
@@ -542,6 +737,13 @@ xlsxturbo.dfs_to_xlsx([
|
|
|
542
737
|
- `table_style` (Excel tables)
|
|
543
738
|
- `freeze_panes`
|
|
544
739
|
- `row_heights`
|
|
740
|
+
- `conditional_formats`
|
|
741
|
+
- `merged_ranges`
|
|
742
|
+
- `hyperlinks`
|
|
743
|
+
- `comments`
|
|
744
|
+
- `validations`
|
|
745
|
+
- `rich_text`
|
|
746
|
+
- `images`
|
|
545
747
|
- `autofit`
|
|
546
748
|
- `conditional_formats`
|
|
547
749
|
- `formula_columns`
|
|
@@ -605,15 +807,16 @@ xlsxturbo sales.csv report.xlsx -d eu -v --sheet-name "Q4 Sales"
|
|
|
605
807
|
|
|
606
808
|
## Performance
|
|
607
809
|
|
|
608
|
-
|
|
810
|
+
*Reference benchmark on 100,000 rows x 50 columns with mixed data types. Your results will vary by system - run the benchmark yourself (see [Benchmarking](#benchmarking)).*
|
|
811
|
+
|
|
812
|
+
| Library | Time (s) | Rows/sec | vs xlsxturbo |
|
|
813
|
+
|---------|----------|----------|--------------|
|
|
814
|
+
| **xlsxturbo** | **6.65** | **15,033** | **1.0x** |
|
|
815
|
+
| polars | 25.07 | 3,988 | 3.8x |
|
|
816
|
+
| pandas + xlsxwriter | 35.60 | 2,809 | 5.4x |
|
|
817
|
+
| pandas + openpyxl | 38.85 | 2,574 | 5.8x |
|
|
609
818
|
|
|
610
|
-
|
|
611
|
-
|--------|------|---------|
|
|
612
|
-
| **xlsxturbo** | 28.5s | **26.7x** |
|
|
613
|
-
| PyExcelerate | 107s | 7.1x |
|
|
614
|
-
| pandas + xlsxwriter | 374s | 2.0x |
|
|
615
|
-
| pandas + openpyxl | 762s | 1.0x |
|
|
616
|
-
| polars.write_excel | 1039s | 0.7x |
|
|
819
|
+
*Test system: Windows 11, Python 3.14, AMD Ryzen 9 (32 threads)*
|
|
617
820
|
|
|
618
821
|
## Type Detection Examples
|
|
619
822
|
|
|
@@ -649,14 +852,24 @@ maturin build --release
|
|
|
649
852
|
|
|
650
853
|
## Benchmarking
|
|
651
854
|
|
|
652
|
-
Run the included benchmark
|
|
855
|
+
Run the included benchmark scripts:
|
|
653
856
|
|
|
654
857
|
```bash
|
|
655
|
-
#
|
|
656
|
-
python benchmark.py
|
|
858
|
+
# Compare xlsxturbo vs other libraries (100K rows default)
|
|
859
|
+
python benchmarks/benchmark.py
|
|
860
|
+
|
|
861
|
+
# Full benchmark: small, medium, large datasets
|
|
862
|
+
python benchmarks/benchmark.py --full
|
|
657
863
|
|
|
658
864
|
# Custom size
|
|
659
|
-
python benchmark.py --rows 500000 --cols 100
|
|
865
|
+
python benchmarks/benchmark.py --rows 500000 --cols 100
|
|
866
|
+
|
|
867
|
+
# Output formats for CI/documentation
|
|
868
|
+
python benchmarks/benchmark.py --markdown
|
|
869
|
+
python benchmarks/benchmark.py --json
|
|
870
|
+
|
|
871
|
+
# Test parallel vs single-threaded CSV conversion
|
|
872
|
+
python benchmarks/benchmark_parallel.py
|
|
660
873
|
```
|
|
661
874
|
|
|
662
875
|
## License
|
|
@@ -10,6 +10,10 @@ High-performance Excel writer with automatic type detection. Written in Rust, us
|
|
|
10
10
|
- **Formula columns** - add calculated columns with Excel formulas
|
|
11
11
|
- **Merged cells** - merge cell ranges for headers and titles
|
|
12
12
|
- **Hyperlinks** - add clickable links to cells
|
|
13
|
+
- **Comments/Notes** - add cell annotations with optional author
|
|
14
|
+
- **Data validation** - dropdowns, number ranges, text length constraints
|
|
15
|
+
- **Rich text** - multiple formats within a single cell
|
|
16
|
+
- **Images** - embed PNG, JPEG, GIF, BMP in cells
|
|
13
17
|
- **Auto-fit columns** - automatically adjust column widths to fit content
|
|
14
18
|
- **Custom column widths** - set specific widths per column or cap all with _all
|
|
15
19
|
- **Header styling** - bold, colors, font size for header row
|
|
@@ -27,7 +31,7 @@ High-performance Excel writer with automatic type detection. Written in Rust, us
|
|
|
27
31
|
- Datetimes (ISO 8601) → Excel datetimes
|
|
28
32
|
- `NaN`/`Inf` → Empty cells (graceful handling)
|
|
29
33
|
- Everything else → Text
|
|
30
|
-
- **~
|
|
34
|
+
- **~6x faster** than pandas + openpyxl (see [benchmarks](#performance))
|
|
31
35
|
- **Memory efficient** - streams data with 1MB buffer
|
|
32
36
|
- Available as both **Python library** and **CLI tool**
|
|
33
37
|
|
|
@@ -312,6 +316,10 @@ Available per-sheet options:
|
|
|
312
316
|
- `formula_columns` (dict): Calculated columns with Excel formulas (column name -> formula template)
|
|
313
317
|
- `merged_ranges` (list): List of (range, text) or (range, text, format) tuples to merge cells
|
|
314
318
|
- `hyperlinks` (list): List of (cell, url) or (cell, url, display_text) tuples to add clickable links
|
|
319
|
+
- `comments` (dict): Cell comments/notes (cell_ref -> text or {text, author})
|
|
320
|
+
- `validations` (dict): Data validation rules (column name/pattern -> validation config)
|
|
321
|
+
- `rich_text` (dict): Rich text with multiple formats (cell_ref -> list of segments)
|
|
322
|
+
- `images` (dict): Embedded images (cell_ref -> path or {path, scale_width, scale_height, alt_text})
|
|
315
323
|
|
|
316
324
|
### Conditional Formatting
|
|
317
325
|
|
|
@@ -482,6 +490,193 @@ xlsxturbo.df_to_xlsx(df, "companies.xlsx",
|
|
|
482
490
|
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
483
491
|
- Not available in constant memory mode
|
|
484
492
|
|
|
493
|
+
### Comments/Notes
|
|
494
|
+
|
|
495
|
+
Add cell annotations (hover to view):
|
|
496
|
+
|
|
497
|
+
```python
|
|
498
|
+
import xlsxturbo
|
|
499
|
+
import pandas as pd
|
|
500
|
+
|
|
501
|
+
df = pd.DataFrame({
|
|
502
|
+
'product': ['Widget A', 'Widget B'],
|
|
503
|
+
'price': [19.99, 29.99]
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
xlsxturbo.df_to_xlsx(df, "report.xlsx",
|
|
507
|
+
comments={
|
|
508
|
+
# Simple text comment
|
|
509
|
+
'A1': 'This column contains product names',
|
|
510
|
+
# Comment with author
|
|
511
|
+
'B1': {'text': 'Prices in USD', 'author': 'Finance Team'}
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Comment format:**
|
|
517
|
+
- Simple: `{'A1': 'Note text'}`
|
|
518
|
+
- With author: `{'A1': {'text': 'Note text', 'author': 'Name'}}`
|
|
519
|
+
|
|
520
|
+
**Notes:**
|
|
521
|
+
- Comments appear as small red triangles in the cell corner
|
|
522
|
+
- Hover over the cell to see the comment
|
|
523
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
524
|
+
- Not available in constant memory mode
|
|
525
|
+
|
|
526
|
+
### Data Validation
|
|
527
|
+
|
|
528
|
+
Add dropdowns and input constraints:
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
import xlsxturbo
|
|
532
|
+
import pandas as pd
|
|
533
|
+
|
|
534
|
+
df = pd.DataFrame({
|
|
535
|
+
'status': ['Open', 'Closed'],
|
|
536
|
+
'score': [85, 92],
|
|
537
|
+
'price': [19.99, 29.99],
|
|
538
|
+
'code': ['ABC', 'XYZ']
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
xlsxturbo.df_to_xlsx(df, "validated.xlsx",
|
|
542
|
+
validations={
|
|
543
|
+
# Dropdown list
|
|
544
|
+
'status': {
|
|
545
|
+
'type': 'list',
|
|
546
|
+
'values': ['Open', 'Closed', 'Pending', 'Review']
|
|
547
|
+
},
|
|
548
|
+
# Whole number range (0-100)
|
|
549
|
+
'score': {
|
|
550
|
+
'type': 'whole_number',
|
|
551
|
+
'min': 0,
|
|
552
|
+
'max': 100,
|
|
553
|
+
'error_title': 'Invalid Score',
|
|
554
|
+
'error_message': 'Score must be between 0 and 100'
|
|
555
|
+
},
|
|
556
|
+
# Decimal range
|
|
557
|
+
'price': {
|
|
558
|
+
'type': 'decimal',
|
|
559
|
+
'min': 0.0,
|
|
560
|
+
'max': 999.99
|
|
561
|
+
},
|
|
562
|
+
# Text length constraint
|
|
563
|
+
'code': {
|
|
564
|
+
'type': 'text_length',
|
|
565
|
+
'min': 3,
|
|
566
|
+
'max': 10
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Validation types:**
|
|
573
|
+
|
|
574
|
+
| Type | Aliases | Description | Options |
|
|
575
|
+
|------|---------|-------------|---------|
|
|
576
|
+
| `list` | - | Dropdown menu | `values` (list of strings, max 255 chars total) |
|
|
577
|
+
| `whole_number` | `whole`, `integer` | Integer range | `min`, `max` |
|
|
578
|
+
| `decimal` | `number` | Decimal range | `min`, `max` |
|
|
579
|
+
| `text_length` | `textlength`, `length` | Character count | `min`, `max` |
|
|
580
|
+
|
|
581
|
+
**Optional message options:**
|
|
582
|
+
- `input_title`, `input_message`: Prompt shown when cell is selected
|
|
583
|
+
- `error_title`, `error_message`: Message shown when invalid data is entered
|
|
584
|
+
|
|
585
|
+
**Notes:**
|
|
586
|
+
- Validations apply to the data rows of the specified column
|
|
587
|
+
- Column patterns work: `'score_*': {...}` matches all columns starting with `score_`
|
|
588
|
+
- If only `min` or only `max` is specified, the other defaults to the type's extreme value
|
|
589
|
+
- List validation values are limited to 255 total characters (Excel limitation)
|
|
590
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
591
|
+
- Not available in constant memory mode
|
|
592
|
+
|
|
593
|
+
### Rich Text
|
|
594
|
+
|
|
595
|
+
Multiple formats within a single cell:
|
|
596
|
+
|
|
597
|
+
```python
|
|
598
|
+
import xlsxturbo
|
|
599
|
+
import pandas as pd
|
|
600
|
+
|
|
601
|
+
df = pd.DataFrame({'A': [1, 2, 3]})
|
|
602
|
+
|
|
603
|
+
xlsxturbo.df_to_xlsx(df, "rich.xlsx",
|
|
604
|
+
rich_text={
|
|
605
|
+
'D1': [
|
|
606
|
+
('Important: ', {'bold': True, 'font_color': 'red'}),
|
|
607
|
+
'Please review ',
|
|
608
|
+
('all', {'italic': True}),
|
|
609
|
+
' values'
|
|
610
|
+
],
|
|
611
|
+
'D2': [
|
|
612
|
+
('Status: ', {'bold': True}),
|
|
613
|
+
('OK', {'font_color': 'green', 'bold': True})
|
|
614
|
+
]
|
|
615
|
+
}
|
|
616
|
+
)
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**Segment format:**
|
|
620
|
+
- Formatted: `('text', {'bold': True, 'font_color': 'blue'})`
|
|
621
|
+
- Plain: `'plain text'` (no formatting)
|
|
622
|
+
|
|
623
|
+
**Available format options:**
|
|
624
|
+
- `bold` (bool)
|
|
625
|
+
- `italic` (bool)
|
|
626
|
+
- `font_color` (str): '#RRGGBB' or named color
|
|
627
|
+
- `bg_color` (str): Background color
|
|
628
|
+
- `font_size` (float)
|
|
629
|
+
- `underline` (bool)
|
|
630
|
+
|
|
631
|
+
**Notes:**
|
|
632
|
+
- Rich text writes to the specified cell position (overwrites existing content)
|
|
633
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
634
|
+
- Not available in constant memory mode
|
|
635
|
+
|
|
636
|
+
### Images
|
|
637
|
+
|
|
638
|
+
Embed images in cells:
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
import xlsxturbo
|
|
642
|
+
import pandas as pd
|
|
643
|
+
|
|
644
|
+
df = pd.DataFrame({'Product': ['Widget A', 'Widget B'], 'Price': [19.99, 29.99]})
|
|
645
|
+
|
|
646
|
+
xlsxturbo.df_to_xlsx(df, "catalog.xlsx",
|
|
647
|
+
autofit=True,
|
|
648
|
+
images={
|
|
649
|
+
# Simple path
|
|
650
|
+
'C2': 'images/widget_a.png',
|
|
651
|
+
# With options
|
|
652
|
+
'C3': {
|
|
653
|
+
'path': 'images/widget_b.png',
|
|
654
|
+
'scale_width': 0.5,
|
|
655
|
+
'scale_height': 0.5,
|
|
656
|
+
'alt_text': 'Widget B photo'
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
)
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Image format:**
|
|
663
|
+
- Simple: `{'C2': 'path/to/image.png'}`
|
|
664
|
+
- With options: `{'C2': {'path': '...', 'scale_width': 0.5, ...}}`
|
|
665
|
+
|
|
666
|
+
**Available options:**
|
|
667
|
+
- `path` (str, required): Path to image file
|
|
668
|
+
- `scale_width` (float): Width scale factor (1.0 = original)
|
|
669
|
+
- `scale_height` (float): Height scale factor (1.0 = original)
|
|
670
|
+
- `alt_text` (str): Alternative text for accessibility
|
|
671
|
+
|
|
672
|
+
**Supported formats:** PNG, JPEG, GIF, BMP
|
|
673
|
+
|
|
674
|
+
**Notes:**
|
|
675
|
+
- Images are positioned at the specified cell (overlays any existing content)
|
|
676
|
+
- Image file must exist; non-existent files will raise an error
|
|
677
|
+
- Works with both `df_to_xlsx` and `dfs_to_xlsx` (global or per-sheet)
|
|
678
|
+
- Not available in constant memory mode
|
|
679
|
+
|
|
485
680
|
### Constant Memory Mode (Large Files)
|
|
486
681
|
|
|
487
682
|
For very large files (millions of rows), use `constant_memory=True` to minimize RAM usage:
|
|
@@ -509,6 +704,13 @@ xlsxturbo.dfs_to_xlsx([
|
|
|
509
704
|
- `table_style` (Excel tables)
|
|
510
705
|
- `freeze_panes`
|
|
511
706
|
- `row_heights`
|
|
707
|
+
- `conditional_formats`
|
|
708
|
+
- `merged_ranges`
|
|
709
|
+
- `hyperlinks`
|
|
710
|
+
- `comments`
|
|
711
|
+
- `validations`
|
|
712
|
+
- `rich_text`
|
|
713
|
+
- `images`
|
|
512
714
|
- `autofit`
|
|
513
715
|
- `conditional_formats`
|
|
514
716
|
- `formula_columns`
|
|
@@ -572,15 +774,16 @@ xlsxturbo sales.csv report.xlsx -d eu -v --sheet-name "Q4 Sales"
|
|
|
572
774
|
|
|
573
775
|
## Performance
|
|
574
776
|
|
|
575
|
-
|
|
777
|
+
*Reference benchmark on 100,000 rows x 50 columns with mixed data types. Your results will vary by system - run the benchmark yourself (see [Benchmarking](#benchmarking)).*
|
|
778
|
+
|
|
779
|
+
| Library | Time (s) | Rows/sec | vs xlsxturbo |
|
|
780
|
+
|---------|----------|----------|--------------|
|
|
781
|
+
| **xlsxturbo** | **6.65** | **15,033** | **1.0x** |
|
|
782
|
+
| polars | 25.07 | 3,988 | 3.8x |
|
|
783
|
+
| pandas + xlsxwriter | 35.60 | 2,809 | 5.4x |
|
|
784
|
+
| pandas + openpyxl | 38.85 | 2,574 | 5.8x |
|
|
576
785
|
|
|
577
|
-
|
|
578
|
-
|--------|------|---------|
|
|
579
|
-
| **xlsxturbo** | 28.5s | **26.7x** |
|
|
580
|
-
| PyExcelerate | 107s | 7.1x |
|
|
581
|
-
| pandas + xlsxwriter | 374s | 2.0x |
|
|
582
|
-
| pandas + openpyxl | 762s | 1.0x |
|
|
583
|
-
| polars.write_excel | 1039s | 0.7x |
|
|
786
|
+
*Test system: Windows 11, Python 3.14, AMD Ryzen 9 (32 threads)*
|
|
584
787
|
|
|
585
788
|
## Type Detection Examples
|
|
586
789
|
|
|
@@ -616,14 +819,24 @@ maturin build --release
|
|
|
616
819
|
|
|
617
820
|
## Benchmarking
|
|
618
821
|
|
|
619
|
-
Run the included benchmark
|
|
822
|
+
Run the included benchmark scripts:
|
|
620
823
|
|
|
621
824
|
```bash
|
|
622
|
-
#
|
|
623
|
-
python benchmark.py
|
|
825
|
+
# Compare xlsxturbo vs other libraries (100K rows default)
|
|
826
|
+
python benchmarks/benchmark.py
|
|
827
|
+
|
|
828
|
+
# Full benchmark: small, medium, large datasets
|
|
829
|
+
python benchmarks/benchmark.py --full
|
|
624
830
|
|
|
625
831
|
# Custom size
|
|
626
|
-
python benchmark.py --rows 500000 --cols 100
|
|
832
|
+
python benchmarks/benchmark.py --rows 500000 --cols 100
|
|
833
|
+
|
|
834
|
+
# Output formats for CI/documentation
|
|
835
|
+
python benchmarks/benchmark.py --markdown
|
|
836
|
+
python benchmarks/benchmark.py --json
|
|
837
|
+
|
|
838
|
+
# Test parallel vs single-threaded CSV conversion
|
|
839
|
+
python benchmarks/benchmark_parallel.py
|
|
627
840
|
```
|
|
628
841
|
|
|
629
842
|
## License
|