python-calamine 0.3.2__tar.gz → 0.5.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.
- {python_calamine-0.3.2 → python_calamine-0.5.0}/.github/workflows/CI.yml +26 -39
- {python_calamine-0.3.2 → python_calamine-0.5.0}/.pre-commit-config.yaml +0 -2
- {python_calamine-0.3.2 → python_calamine-0.5.0}/Cargo.lock +42 -68
- python_calamine-0.5.0/Cargo.toml +22 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/PKG-INFO +6 -26
- {python_calamine-0.3.2 → python_calamine-0.5.0}/README.md +0 -13
- {python_calamine-0.3.2 → python_calamine-0.5.0}/pyproject.toml +9 -9
- {python_calamine-0.3.2 → python_calamine-0.5.0}/python/python_calamine/_python_calamine.pyi +12 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/lib.rs +1 -1
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/types/cell.rs +32 -4
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/types/sheet.rs +19 -4
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/types/workbook.rs +56 -20
- python_calamine-0.5.0/tests/data/base.ods +0 -0
- python_calamine-0.5.0/tests/data/base.xls +0 -0
- python_calamine-0.5.0/tests/data/base.xlsb +0 -0
- python_calamine-0.5.0/tests/data/base.xlsx +0 -0
- python_calamine-0.5.0/tests/data/issue139.xlsx +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/test_base.py +24 -5
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/test_pandas.py +0 -13
- python_calamine-0.3.2/Cargo.toml +0 -27
- python_calamine-0.3.2/python/python_calamine/pandas.py +0 -136
- python_calamine-0.3.2/tests/data/base.ods +0 -0
- python_calamine-0.3.2/tests/data/base.xls +0 -0
- python_calamine-0.3.2/tests/data/base.xlsb +0 -0
- python_calamine-0.3.2/tests/data/base.xlsx +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/.gitignore +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/LICENSE +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/build.rs +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/python/python_calamine/__init__.py +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/python/python_calamine/py.typed +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/renovate.json +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/types/errors.rs +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/types/mod.rs +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/src/utils.rs +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/__init__.py +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/conftest.py +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/any_sheets.ods +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/any_sheets.xls +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/any_sheets.xlsb +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/any_sheets.xlsx +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/empty_file.ods +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/empty_file.xlsb +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/empty_file.xlsx +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/password.ods +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/password.xls +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/password.xlsb +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/data/password.xlsx +0 -0
- {python_calamine-0.3.2 → python_calamine-0.5.0}/tests/test_sheet_type.py +0 -0
|
@@ -13,33 +13,29 @@ on:
|
|
|
13
13
|
|
|
14
14
|
jobs:
|
|
15
15
|
test:
|
|
16
|
-
name: test ${{ matrix.python-version }} pandas ${{ matrix.pandas-version }}
|
|
16
|
+
name: test ${{ matrix.python-version }} pandas ${{ matrix.pandas-version }}
|
|
17
17
|
strategy:
|
|
18
18
|
fail-fast: false
|
|
19
19
|
matrix:
|
|
20
20
|
python-version:
|
|
21
|
-
- "3.9"
|
|
22
21
|
- "3.10"
|
|
23
22
|
- "3.11"
|
|
24
23
|
- "3.12"
|
|
24
|
+
- "3.13"
|
|
25
|
+
- "3.14"
|
|
25
26
|
pandas-version:
|
|
26
|
-
- "2.1.*"
|
|
27
27
|
- "2.2.*"
|
|
28
|
+
- "2.3.*"
|
|
28
29
|
include:
|
|
29
|
-
- python-version: "3.8"
|
|
30
|
-
pandas-version: "none"
|
|
31
30
|
# https://github.com/pandas-dev/pandas/issues/42509
|
|
32
|
-
- python-version: "pypy3.
|
|
31
|
+
- python-version: "pypy3.11"
|
|
33
32
|
pandas-version: "none"
|
|
34
|
-
# https://github.com/pandas-dev/pandas/issues/
|
|
35
|
-
- python-version: "
|
|
33
|
+
# https://github.com/pandas-dev/pandas/issues/60016
|
|
34
|
+
- python-version: "3.13t"
|
|
36
35
|
pandas-version: "none"
|
|
37
|
-
- python-version: "3.13"
|
|
38
|
-
pandas-version: "2.2.*"
|
|
39
36
|
# https://github.com/pandas-dev/pandas/issues/60016
|
|
40
|
-
- python-version: "3.
|
|
37
|
+
- python-version: "3.14t"
|
|
41
38
|
pandas-version: "none"
|
|
42
|
-
disable-gil: true
|
|
43
39
|
|
|
44
40
|
runs-on: ubuntu-latest
|
|
45
41
|
|
|
@@ -47,7 +43,7 @@ jobs:
|
|
|
47
43
|
PYTHON: ${{ matrix.python-version }}
|
|
48
44
|
|
|
49
45
|
steps:
|
|
50
|
-
- uses: actions/checkout@
|
|
46
|
+
- uses: actions/checkout@v5
|
|
51
47
|
|
|
52
48
|
- name: install rust stable
|
|
53
49
|
uses: dtolnay/rust-toolchain@master
|
|
@@ -61,29 +57,22 @@ jobs:
|
|
|
61
57
|
key: v3
|
|
62
58
|
|
|
63
59
|
- name: set up python
|
|
64
|
-
uses: actions/setup-python@
|
|
60
|
+
uses: actions/setup-python@v6
|
|
65
61
|
if: "${{ !matrix.disable-gil }}"
|
|
66
62
|
with:
|
|
67
63
|
python-version: ${{ matrix.python-version }}
|
|
68
64
|
allow-prereleases: true
|
|
69
65
|
|
|
70
|
-
- name: set up python (free-threaded)
|
|
71
|
-
uses: deadsnakes/action@v3.2.0
|
|
72
|
-
if: "${{ matrix.disable-gil }}"
|
|
73
|
-
with:
|
|
74
|
-
python-version: ${{ matrix.python-version }}
|
|
75
|
-
nogil: ${{ matrix.disable-gil }}
|
|
76
|
-
|
|
77
66
|
- id: cache-py
|
|
78
67
|
name: cache python
|
|
79
68
|
uses: actions/cache@v4
|
|
80
|
-
if: "${{ !matrix.disable-gil }}"
|
|
81
69
|
with:
|
|
82
70
|
path: ${{ env.pythonLocation }}
|
|
83
71
|
key: >
|
|
84
72
|
py
|
|
85
73
|
${{ runner.os }}
|
|
86
|
-
${{
|
|
74
|
+
${{ matrix.python-version }}
|
|
75
|
+
${{ matrix.pandas-version }}
|
|
87
76
|
|
|
88
77
|
- run: |
|
|
89
78
|
if [ ${{ matrix.pandas-version }} == 'none' ]; then
|
|
@@ -105,7 +94,7 @@ jobs:
|
|
|
105
94
|
runs-on: ubuntu-latest
|
|
106
95
|
|
|
107
96
|
steps:
|
|
108
|
-
- uses: actions/checkout@
|
|
97
|
+
- uses: actions/checkout@v5
|
|
109
98
|
|
|
110
99
|
- name: install rust stable
|
|
111
100
|
uses: dtolnay/rust-toolchain@stable
|
|
@@ -115,7 +104,7 @@ jobs:
|
|
|
115
104
|
- name: cache rust
|
|
116
105
|
uses: Swatinem/rust-cache@v2
|
|
117
106
|
|
|
118
|
-
- uses: actions/setup-python@
|
|
107
|
+
- uses: actions/setup-python@v6
|
|
119
108
|
with:
|
|
120
109
|
python-version: "3.13"
|
|
121
110
|
|
|
@@ -155,7 +144,7 @@ jobs:
|
|
|
155
144
|
strategy:
|
|
156
145
|
fail-fast: false
|
|
157
146
|
matrix:
|
|
158
|
-
os: [ubuntu, macos
|
|
147
|
+
os: [ubuntu, macos]
|
|
159
148
|
target: [x86_64, aarch64]
|
|
160
149
|
manylinux: [auto]
|
|
161
150
|
include:
|
|
@@ -163,15 +152,17 @@ jobs:
|
|
|
163
152
|
platform: linux
|
|
164
153
|
- os: windows
|
|
165
154
|
ls: dir
|
|
155
|
+
target: x86_64
|
|
156
|
+
interpreter: 3.10 3.11 3.12 3.13 3.13t
|
|
166
157
|
- os: windows
|
|
167
158
|
ls: dir
|
|
168
159
|
target: i686
|
|
169
160
|
python-architecture: x86
|
|
170
|
-
interpreter: 3.
|
|
161
|
+
interpreter: 3.10 3.11 3.12 3.13 3.13t
|
|
171
162
|
- os: windows
|
|
172
163
|
ls: dir
|
|
173
164
|
target: aarch64
|
|
174
|
-
interpreter: 3.11 3.12 3.13
|
|
165
|
+
interpreter: 3.11 3.12 3.13 3.13t
|
|
175
166
|
- os: ubuntu
|
|
176
167
|
platform: linux
|
|
177
168
|
target: i686
|
|
@@ -181,7 +172,7 @@ jobs:
|
|
|
181
172
|
- os: ubuntu
|
|
182
173
|
platform: linux
|
|
183
174
|
target: armv7
|
|
184
|
-
interpreter: 3.
|
|
175
|
+
interpreter: 3.10 3.11 3.12 3.13 3.13t 3.14 3.14t pypy3.11
|
|
185
176
|
# musllinux
|
|
186
177
|
- os: ubuntu
|
|
187
178
|
platform: linux
|
|
@@ -194,22 +185,18 @@ jobs:
|
|
|
194
185
|
- os: ubuntu
|
|
195
186
|
platform: linux
|
|
196
187
|
target: ppc64le
|
|
197
|
-
interpreter: 3.
|
|
188
|
+
interpreter: 3.10 3.11 3.12 3.13 3.13t 3.14 3.14t pypy3.11
|
|
198
189
|
- os: ubuntu
|
|
199
190
|
platform: linux
|
|
200
191
|
target: s390x
|
|
201
|
-
interpreter: 3.
|
|
202
|
-
exclude:
|
|
203
|
-
# Windows on arm64 only supports Python 3.11+
|
|
204
|
-
- os: windows
|
|
205
|
-
target: aarch64
|
|
192
|
+
interpreter: 3.10 3.11 3.12 3.13 3.13t 3.14 3.14t pypy3.11
|
|
206
193
|
|
|
207
194
|
runs-on: ${{ matrix.os }}-latest
|
|
208
195
|
steps:
|
|
209
|
-
- uses: actions/checkout@
|
|
196
|
+
- uses: actions/checkout@v5
|
|
210
197
|
|
|
211
198
|
- name: set up python
|
|
212
|
-
uses: actions/setup-python@
|
|
199
|
+
uses: actions/setup-python@v6
|
|
213
200
|
with:
|
|
214
201
|
python-version: "3.13"
|
|
215
202
|
architecture: ${{ matrix.python-architecture || 'x64' }}
|
|
@@ -229,7 +216,7 @@ jobs:
|
|
|
229
216
|
with:
|
|
230
217
|
target: ${{ matrix.target }}
|
|
231
218
|
manylinux: ${{ matrix.manylinux || 'auto' }}
|
|
232
|
-
args: --release --out dist --interpreter ${{ matrix.interpreter || '3.
|
|
219
|
+
args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12 3.13 3.13t 3.14 3.14t pypy3.11' }}
|
|
233
220
|
rust-toolchain: stable
|
|
234
221
|
|
|
235
222
|
- run: ${{ matrix.ls || 'ls -lh' }} dist/
|
|
@@ -256,7 +243,7 @@ jobs:
|
|
|
256
243
|
|
|
257
244
|
steps:
|
|
258
245
|
- name: Download All Artifacts
|
|
259
|
-
uses: actions/download-artifact@
|
|
246
|
+
uses: actions/download-artifact@v5
|
|
260
247
|
with:
|
|
261
248
|
path: dist
|
|
262
249
|
pattern: wheels-*
|
|
@@ -70,8 +70,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
|
70
70
|
|
|
71
71
|
[[package]]
|
|
72
72
|
name = "calamine"
|
|
73
|
-
version = "0.
|
|
74
|
-
source = "
|
|
73
|
+
version = "0.30.0"
|
|
74
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
75
|
+
checksum = "a9aeb09f84576a634da713630e11e431a744b91f1f8114c2ff0760189783a8a1"
|
|
75
76
|
dependencies = [
|
|
76
77
|
"atoi_simd",
|
|
77
78
|
"byteorder",
|
|
@@ -133,9 +134,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
|
133
134
|
|
|
134
135
|
[[package]]
|
|
135
136
|
name = "chrono"
|
|
136
|
-
version = "0.4.
|
|
137
|
+
version = "0.4.41"
|
|
137
138
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
138
|
-
checksum = "
|
|
139
|
+
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
|
139
140
|
dependencies = [
|
|
140
141
|
"android-tzdata",
|
|
141
142
|
"iana-time-zone",
|
|
@@ -170,12 +171,6 @@ dependencies = [
|
|
|
170
171
|
"cfg-if",
|
|
171
172
|
]
|
|
172
173
|
|
|
173
|
-
[[package]]
|
|
174
|
-
name = "crossbeam-utils"
|
|
175
|
-
version = "0.8.21"
|
|
176
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
177
|
-
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
178
|
-
|
|
179
174
|
[[package]]
|
|
180
175
|
name = "derive_arbitrary"
|
|
181
176
|
version = "1.4.1"
|
|
@@ -187,17 +182,6 @@ dependencies = [
|
|
|
187
182
|
"syn",
|
|
188
183
|
]
|
|
189
184
|
|
|
190
|
-
[[package]]
|
|
191
|
-
name = "displaydoc"
|
|
192
|
-
version = "0.2.5"
|
|
193
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
194
|
-
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|
195
|
-
dependencies = [
|
|
196
|
-
"proc-macro2",
|
|
197
|
-
"quote",
|
|
198
|
-
"syn",
|
|
199
|
-
]
|
|
200
|
-
|
|
201
185
|
[[package]]
|
|
202
186
|
name = "encoding_rs"
|
|
203
187
|
version = "0.8.35"
|
|
@@ -246,11 +230,12 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|
|
246
230
|
|
|
247
231
|
[[package]]
|
|
248
232
|
name = "flate2"
|
|
249
|
-
version = "1.
|
|
233
|
+
version = "1.1.2"
|
|
250
234
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
251
|
-
checksum = "
|
|
235
|
+
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
|
252
236
|
dependencies = [
|
|
253
237
|
"crc32fast",
|
|
238
|
+
"libz-rs-sys",
|
|
254
239
|
"miniz_oxide",
|
|
255
240
|
]
|
|
256
241
|
|
|
@@ -344,6 +329,15 @@ version = "0.2.169"
|
|
|
344
329
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
345
330
|
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
|
346
331
|
|
|
332
|
+
[[package]]
|
|
333
|
+
name = "libz-rs-sys"
|
|
334
|
+
version = "0.5.1"
|
|
335
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
336
|
+
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
|
|
337
|
+
dependencies = [
|
|
338
|
+
"zlib-rs",
|
|
339
|
+
]
|
|
340
|
+
|
|
347
341
|
[[package]]
|
|
348
342
|
name = "linux-raw-sys"
|
|
349
343
|
version = "0.4.15"
|
|
@@ -379,9 +373,9 @@ dependencies = [
|
|
|
379
373
|
|
|
380
374
|
[[package]]
|
|
381
375
|
name = "miniz_oxide"
|
|
382
|
-
version = "0.8.
|
|
376
|
+
version = "0.8.9"
|
|
383
377
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
384
|
-
checksum = "
|
|
378
|
+
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
|
385
379
|
dependencies = [
|
|
386
380
|
"adler2",
|
|
387
381
|
]
|
|
@@ -397,9 +391,9 @@ dependencies = [
|
|
|
397
391
|
|
|
398
392
|
[[package]]
|
|
399
393
|
name = "once_cell"
|
|
400
|
-
version = "1.
|
|
394
|
+
version = "1.21.3"
|
|
401
395
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
402
|
-
checksum = "
|
|
396
|
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
403
397
|
|
|
404
398
|
[[package]]
|
|
405
399
|
name = "portable-atomic"
|
|
@@ -429,11 +423,10 @@ dependencies = [
|
|
|
429
423
|
|
|
430
424
|
[[package]]
|
|
431
425
|
name = "pyo3"
|
|
432
|
-
version = "0.
|
|
426
|
+
version = "0.26.0"
|
|
433
427
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
434
|
-
checksum = "
|
|
428
|
+
checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383"
|
|
435
429
|
dependencies = [
|
|
436
|
-
"cfg-if",
|
|
437
430
|
"chrono",
|
|
438
431
|
"indoc",
|
|
439
432
|
"libc",
|
|
@@ -448,20 +441,19 @@ dependencies = [
|
|
|
448
441
|
|
|
449
442
|
[[package]]
|
|
450
443
|
name = "pyo3-build-config"
|
|
451
|
-
version = "0.
|
|
444
|
+
version = "0.26.0"
|
|
452
445
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
453
|
-
checksum = "
|
|
446
|
+
checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f"
|
|
454
447
|
dependencies = [
|
|
455
|
-
"once_cell",
|
|
456
448
|
"python3-dll-a",
|
|
457
449
|
"target-lexicon",
|
|
458
450
|
]
|
|
459
451
|
|
|
460
452
|
[[package]]
|
|
461
453
|
name = "pyo3-ffi"
|
|
462
|
-
version = "0.
|
|
454
|
+
version = "0.26.0"
|
|
463
455
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
464
|
-
checksum = "
|
|
456
|
+
checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105"
|
|
465
457
|
dependencies = [
|
|
466
458
|
"libc",
|
|
467
459
|
"pyo3-build-config",
|
|
@@ -469,9 +461,8 @@ dependencies = [
|
|
|
469
461
|
|
|
470
462
|
[[package]]
|
|
471
463
|
name = "pyo3-file"
|
|
472
|
-
version = "0.
|
|
473
|
-
source = "
|
|
474
|
-
checksum = "8d97fbb05588c29774af01833e598fae7e714a3f87d36b286e0466f6b678e4a7"
|
|
464
|
+
version = "0.13.0"
|
|
465
|
+
source = "git+https://github.com/dimastbk/pyo3-file.git?rev=da8ddb5d774d042555b867dd6571f3ce128bc349#da8ddb5d774d042555b867dd6571f3ce128bc349"
|
|
475
466
|
dependencies = [
|
|
476
467
|
"pyo3",
|
|
477
468
|
"skeptic",
|
|
@@ -479,9 +470,9 @@ dependencies = [
|
|
|
479
470
|
|
|
480
471
|
[[package]]
|
|
481
472
|
name = "pyo3-macros"
|
|
482
|
-
version = "0.
|
|
473
|
+
version = "0.26.0"
|
|
483
474
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
484
|
-
checksum = "
|
|
475
|
+
checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded"
|
|
485
476
|
dependencies = [
|
|
486
477
|
"proc-macro2",
|
|
487
478
|
"pyo3-macros-backend",
|
|
@@ -491,9 +482,9 @@ dependencies = [
|
|
|
491
482
|
|
|
492
483
|
[[package]]
|
|
493
484
|
name = "pyo3-macros-backend"
|
|
494
|
-
version = "0.
|
|
485
|
+
version = "0.26.0"
|
|
495
486
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
496
|
-
checksum = "
|
|
487
|
+
checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf"
|
|
497
488
|
dependencies = [
|
|
498
489
|
"heck",
|
|
499
490
|
"proc-macro2",
|
|
@@ -504,7 +495,7 @@ dependencies = [
|
|
|
504
495
|
|
|
505
496
|
[[package]]
|
|
506
497
|
name = "python-calamine"
|
|
507
|
-
version = "0.
|
|
498
|
+
version = "0.5.0"
|
|
508
499
|
dependencies = [
|
|
509
500
|
"calamine",
|
|
510
501
|
"chrono",
|
|
@@ -668,26 +659,6 @@ dependencies = [
|
|
|
668
659
|
"windows-sys",
|
|
669
660
|
]
|
|
670
661
|
|
|
671
|
-
[[package]]
|
|
672
|
-
name = "thiserror"
|
|
673
|
-
version = "2.0.10"
|
|
674
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
675
|
-
checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3"
|
|
676
|
-
dependencies = [
|
|
677
|
-
"thiserror-impl",
|
|
678
|
-
]
|
|
679
|
-
|
|
680
|
-
[[package]]
|
|
681
|
-
name = "thiserror-impl"
|
|
682
|
-
version = "2.0.10"
|
|
683
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
684
|
-
checksum = "9e9465d30713b56a37ede7185763c3492a91be2f5fa68d958c44e41ab9248beb"
|
|
685
|
-
dependencies = [
|
|
686
|
-
"proc-macro2",
|
|
687
|
-
"quote",
|
|
688
|
-
"syn",
|
|
689
|
-
]
|
|
690
|
-
|
|
691
662
|
[[package]]
|
|
692
663
|
name = "unicase"
|
|
693
664
|
version = "2.8.1"
|
|
@@ -881,21 +852,24 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
|
881
852
|
|
|
882
853
|
[[package]]
|
|
883
854
|
name = "zip"
|
|
884
|
-
version = "
|
|
855
|
+
version = "4.5.0"
|
|
885
856
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
886
|
-
checksum = "
|
|
857
|
+
checksum = "8835eb39822904d39cb19465de1159e05d371973f0c6df3a365ad50565ddc8b9"
|
|
887
858
|
dependencies = [
|
|
888
859
|
"arbitrary",
|
|
889
860
|
"crc32fast",
|
|
890
|
-
"crossbeam-utils",
|
|
891
|
-
"displaydoc",
|
|
892
861
|
"flate2",
|
|
893
862
|
"indexmap",
|
|
894
863
|
"memchr",
|
|
895
|
-
"thiserror",
|
|
896
864
|
"zopfli",
|
|
897
865
|
]
|
|
898
866
|
|
|
867
|
+
[[package]]
|
|
868
|
+
name = "zlib-rs"
|
|
869
|
+
version = "0.5.1"
|
|
870
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
871
|
+
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
|
|
872
|
+
|
|
899
873
|
[[package]]
|
|
900
874
|
name = "zopfli"
|
|
901
875
|
version = "0.8.1"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "python-calamine"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "python_calamine"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
calamine = { version = "0.30.0", features = ["dates"] }
|
|
13
|
+
pyo3 = { version = "0.26.0", features = [
|
|
14
|
+
"extension-module",
|
|
15
|
+
"chrono",
|
|
16
|
+
"generate-import-lib",
|
|
17
|
+
] }
|
|
18
|
+
chrono = { version = "0.4.38", features = ["serde"] }
|
|
19
|
+
pyo3-file = { git = "https://github.com/dimastbk/pyo3-file.git", rev = "da8ddb5d774d042555b867dd6571f3ce128bc349" }
|
|
20
|
+
|
|
21
|
+
[build-dependencies]
|
|
22
|
+
pyo3-build-config = "0.26.0"
|
|
@@ -1,31 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-calamine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
6
5
|
Classifier: Programming Language :: Rust
|
|
7
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
6
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
7
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
8
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
9
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
11
|
Requires-Dist: maturin~=1.0 ; extra == 'dev'
|
|
15
|
-
Requires-Dist: pre-commit~=3
|
|
12
|
+
Requires-Dist: pre-commit~=4.3 ; extra == 'dev'
|
|
16
13
|
Requires-Dist: pytest~=8.0 ; extra == 'dev'
|
|
17
|
-
Requires-Dist: pandas[excel]~=2.
|
|
18
|
-
Requires-Dist: numpy~=1.0 ; extra == 'dev'
|
|
14
|
+
Requires-Dist: pandas[excel]~=2.2 ; extra == 'dev'
|
|
19
15
|
Provides-Extra: dev
|
|
20
16
|
License-File: LICENSE
|
|
21
17
|
Summary: Python binding for Rust's library for reading excel and odf file - calamine
|
|
22
|
-
Home-Page: https://github.com/dimastbk/python-calamine
|
|
23
|
-
Author: Dmitriy <dimastbk@proton.me>
|
|
24
18
|
Author-email: Dmitriy <dimastbk@proton.me>
|
|
25
|
-
License: MIT
|
|
26
|
-
Requires-Python: >=3.
|
|
19
|
+
License-Expression: MIT
|
|
20
|
+
Requires-Python: >=3.10
|
|
27
21
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
28
|
-
Project-URL: Source Code, https://github.com/dimastbk/python-calamine
|
|
29
22
|
|
|
30
23
|
# python-calamine
|
|
31
24
|
[](https://pypi.org/project/python-calamine/)
|
|
@@ -79,20 +72,7 @@ workbook = CalamineWorkbook.from_path("file.xlsx").get_sheet_by_name("Sheet1").t
|
|
|
79
72
|
# ]
|
|
80
73
|
```
|
|
81
74
|
|
|
82
|
-
Also, you can use monkeypatch for pandas for use this library as engine in `read_excel()` (only pandas 2.0 and 2.1 are supported).
|
|
83
75
|
Pandas 2.2 and above have built-in support of python-calamine.
|
|
84
|
-
```python
|
|
85
|
-
from pandas import read_excel
|
|
86
|
-
from python_calamine.pandas import pandas_monkeypatch
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pandas_monkeypatch()
|
|
90
|
-
|
|
91
|
-
read_excel("file.xlsx", engine="calamine")
|
|
92
|
-
# 1 2 3 4 5 6 7
|
|
93
|
-
# 0 1 2 3 4 5 6 7
|
|
94
|
-
# 1 1 2 3 4 5 6 7
|
|
95
|
-
```
|
|
96
76
|
|
|
97
77
|
Also, you can find additional examples in [tests](https://github.com/dimastbk/python-calamine/blob/master/tests/test_base.py).
|
|
98
78
|
|
|
@@ -50,20 +50,7 @@ workbook = CalamineWorkbook.from_path("file.xlsx").get_sheet_by_name("Sheet1").t
|
|
|
50
50
|
# ]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Also, you can use monkeypatch for pandas for use this library as engine in `read_excel()` (only pandas 2.0 and 2.1 are supported).
|
|
54
53
|
Pandas 2.2 and above have built-in support of python-calamine.
|
|
55
|
-
```python
|
|
56
|
-
from pandas import read_excel
|
|
57
|
-
from python_calamine.pandas import pandas_monkeypatch
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
pandas_monkeypatch()
|
|
61
|
-
|
|
62
|
-
read_excel("file.xlsx", engine="calamine")
|
|
63
|
-
# 1 2 3 4 5 6 7
|
|
64
|
-
# 0 1 2 3 4 5 6 7
|
|
65
|
-
# 1 1 2 3 4 5 6 7
|
|
66
|
-
```
|
|
67
54
|
|
|
68
55
|
Also, you can find additional examples in [tests](https://github.com/dimastbk/python-calamine/blob/master/tests/test_base.py).
|
|
69
56
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-calamine"
|
|
3
|
-
|
|
3
|
+
description = "Python binding for Rust's library for reading excel and odf file - calamine"
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "Dmitriy", email = "dimastbk@proton.me" }]
|
|
4
8
|
classifiers = [
|
|
5
9
|
"Development Status :: 4 - Beta",
|
|
6
|
-
"License :: OSI Approved :: MIT License",
|
|
7
10
|
"Programming Language :: Rust",
|
|
8
|
-
"Programming Language :: Python :: 3.8",
|
|
9
|
-
"Programming Language :: Python :: 3.9",
|
|
10
11
|
"Programming Language :: Python :: 3.10",
|
|
11
12
|
"Programming Language :: Python :: 3.11",
|
|
12
13
|
"Programming Language :: Python :: 3.12",
|
|
13
14
|
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Programming Language :: Python :: 3.14",
|
|
14
16
|
]
|
|
15
|
-
dependencies = ["packaging>=23.1"]
|
|
16
17
|
dynamic = ["version"]
|
|
17
18
|
|
|
18
19
|
[project.optional-dependencies]
|
|
19
20
|
dev = [
|
|
20
21
|
"maturin~=1.0",
|
|
21
|
-
"pre-commit~=3
|
|
22
|
+
"pre-commit~=4.3",
|
|
22
23
|
"pytest~=8.0",
|
|
23
|
-
"pandas[excel]~=2.
|
|
24
|
-
"numpy~=1.0",
|
|
24
|
+
"pandas[excel]~=2.2",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[tool.isort]
|
|
@@ -31,7 +31,7 @@ multi_line_output = 3
|
|
|
31
31
|
profile = "black"
|
|
32
32
|
|
|
33
33
|
[tool.mypy]
|
|
34
|
-
python_version = "3.
|
|
34
|
+
python_version = "3.10"
|
|
35
35
|
ignore_missing_imports = false
|
|
36
36
|
disallow_untyped_defs = true
|
|
37
37
|
check_untyped_defs = true
|
|
@@ -89,6 +89,18 @@ class CalamineSheet:
|
|
|
89
89
|
]:
|
|
90
90
|
"""Retunrning data from sheet as iterator of lists."""
|
|
91
91
|
|
|
92
|
+
@property
|
|
93
|
+
def merged_cell_ranges(
|
|
94
|
+
self,
|
|
95
|
+
) -> list[tuple[tuple[int, int], tuple[int, int]]] | None:
|
|
96
|
+
"""Return a copy of merged cell ranges.
|
|
97
|
+
|
|
98
|
+
Support only for xlsx/xls.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
list of merged cell ranges (tuple[start coordinate, end coordinate]) or None for unsuported format
|
|
102
|
+
"""
|
|
103
|
+
|
|
92
104
|
@typing.final
|
|
93
105
|
class CalamineWorkbook(contextlib.AbstractContextManager):
|
|
94
106
|
path: str | None
|
|
@@ -8,7 +8,7 @@ use crate::types::{
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
#[pyfunction]
|
|
11
|
-
fn load_workbook(py: Python, path_or_filelike:
|
|
11
|
+
fn load_workbook(py: Python, path_or_filelike: Py<PyAny>) -> PyResult<CalamineWorkbook> {
|
|
12
12
|
CalamineWorkbook::from_object(py, path_or_filelike)
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
use std::convert::From;
|
|
2
2
|
|
|
3
3
|
use calamine::DataType;
|
|
4
|
+
use chrono::Datelike;
|
|
4
5
|
use pyo3::prelude::*;
|
|
5
6
|
|
|
7
|
+
/// https://docs.python.org/3/library/datetime.html#constants
|
|
8
|
+
/// The smallest year number allowed in a date or datetime object. MINYEAR is 1.
|
|
9
|
+
const MINYEAR: i32 = 1;
|
|
10
|
+
|
|
11
|
+
/// The largest year number allowed in a date or datetime object. MAXYEAR is 9999.
|
|
12
|
+
const MAXYEAR: i32 = 9999;
|
|
13
|
+
|
|
6
14
|
#[derive(Debug, Clone)]
|
|
7
15
|
pub enum CellValue {
|
|
8
16
|
Int(i64),
|
|
@@ -16,6 +24,14 @@ pub enum CellValue {
|
|
|
16
24
|
Empty,
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
fn check_year_range<DT: Datelike>(value: DT) -> Option<DT> {
|
|
28
|
+
if value.year() < MINYEAR || value.year() > MAXYEAR {
|
|
29
|
+
None
|
|
30
|
+
} else {
|
|
31
|
+
Some(value)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
impl<'py> IntoPyObject<'py> for CellValue {
|
|
20
36
|
type Target = PyAny;
|
|
21
37
|
type Output = Bound<'py, Self::Target>;
|
|
@@ -64,19 +80,31 @@ where
|
|
|
64
80
|
} else if v < 1.0 {
|
|
65
81
|
value.as_time().map(CellValue::Time)
|
|
66
82
|
} else if v == (v as u64) as f64 {
|
|
67
|
-
value
|
|
83
|
+
value
|
|
84
|
+
.as_date()
|
|
85
|
+
.and_then(check_year_range)
|
|
86
|
+
.map(CellValue::Date)
|
|
68
87
|
} else {
|
|
69
|
-
value
|
|
88
|
+
value
|
|
89
|
+
.as_datetime()
|
|
90
|
+
.and_then(check_year_range)
|
|
91
|
+
.map(CellValue::DateTime)
|
|
70
92
|
}
|
|
71
93
|
.unwrap_or(CellValue::Float(v))
|
|
72
94
|
} else if value.is_datetime_iso() {
|
|
73
95
|
let v = value.get_datetime_iso().unwrap();
|
|
74
96
|
if v.contains('T') {
|
|
75
|
-
value
|
|
97
|
+
value
|
|
98
|
+
.as_datetime()
|
|
99
|
+
.and_then(check_year_range)
|
|
100
|
+
.map(CellValue::DateTime)
|
|
76
101
|
} else if v.contains(':') {
|
|
77
102
|
value.as_time().map(CellValue::Time)
|
|
78
103
|
} else {
|
|
79
|
-
value
|
|
104
|
+
value
|
|
105
|
+
.as_date()
|
|
106
|
+
.and_then(check_year_range)
|
|
107
|
+
.map(CellValue::Date)
|
|
80
108
|
}
|
|
81
109
|
.unwrap_or(CellValue::String(v.to_owned()))
|
|
82
110
|
} else if value.is_duration_iso() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use std::fmt::Display;
|
|
2
2
|
use std::sync::Arc;
|
|
3
3
|
|
|
4
|
-
use calamine::{Data, Range, Rows, SheetType, SheetVisible};
|
|
4
|
+
use calamine::{Data, Dimensions, Range, Rows, SheetType, SheetVisible};
|
|
5
5
|
use pyo3::class::basic::CompareOp;
|
|
6
6
|
use pyo3::prelude::*;
|
|
7
7
|
use pyo3::types::PyList;
|
|
@@ -25,7 +25,7 @@ pub enum SheetTypeEnum {
|
|
|
25
25
|
|
|
26
26
|
impl Display for SheetTypeEnum {
|
|
27
27
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
28
|
-
write!(f, "SheetTypeEnum.{:?}"
|
|
28
|
+
write!(f, "SheetTypeEnum.{self:?}")
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -54,7 +54,7 @@ pub enum SheetVisibleEnum {
|
|
|
54
54
|
|
|
55
55
|
impl Display for SheetVisibleEnum {
|
|
56
56
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
57
|
-
write!(f, "SheetVisibleEnum.{:?}"
|
|
57
|
+
write!(f, "SheetVisibleEnum.{self:?}")
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -68,6 +68,8 @@ impl From<SheetVisible> for SheetVisibleEnum {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
type MergedCellRange = ((u32, u32), (u32, u32));
|
|
72
|
+
|
|
71
73
|
#[pyclass]
|
|
72
74
|
#[derive(Clone, PartialEq)]
|
|
73
75
|
pub struct SheetMetadata {
|
|
@@ -132,13 +134,19 @@ pub struct CalamineSheet {
|
|
|
132
134
|
#[pyo3(get)]
|
|
133
135
|
name: String,
|
|
134
136
|
range: Arc<Range<Data>>,
|
|
137
|
+
merged_cell_ranges: Option<Vec<Dimensions>>,
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
impl CalamineSheet {
|
|
138
|
-
pub fn new(
|
|
141
|
+
pub fn new(
|
|
142
|
+
name: String,
|
|
143
|
+
range: Range<Data>,
|
|
144
|
+
merged_cell_ranges: Option<Vec<Dimensions>>,
|
|
145
|
+
) -> Self {
|
|
139
146
|
CalamineSheet {
|
|
140
147
|
name,
|
|
141
148
|
range: Arc::new(range),
|
|
149
|
+
merged_cell_ranges,
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
}
|
|
@@ -212,6 +220,13 @@ impl CalamineSheet {
|
|
|
212
220
|
fn iter_rows(&self) -> CalamineCellIterator {
|
|
213
221
|
CalamineCellIterator::from_range(Arc::clone(&self.range))
|
|
214
222
|
}
|
|
223
|
+
|
|
224
|
+
#[getter]
|
|
225
|
+
fn merged_cell_ranges(slf: PyRef<'_, Self>) -> Option<Vec<MergedCellRange>> {
|
|
226
|
+
slf.merged_cell_ranges
|
|
227
|
+
.as_ref()
|
|
228
|
+
.map(|r| r.iter().map(|d| (d.start, d.end)).collect())
|
|
229
|
+
}
|
|
215
230
|
}
|
|
216
231
|
|
|
217
232
|
#[pyclass]
|
|
@@ -2,7 +2,9 @@ use std::fs::File;
|
|
|
2
2
|
use std::io::{BufReader, Cursor, Read};
|
|
3
3
|
use std::path::PathBuf;
|
|
4
4
|
|
|
5
|
-
use calamine::{
|
|
5
|
+
use calamine::{
|
|
6
|
+
open_workbook_auto, open_workbook_auto_from_rs, Error as CalamineCrateError, Reader, Sheets,
|
|
7
|
+
};
|
|
6
8
|
use pyo3::exceptions::PyTypeError;
|
|
7
9
|
use pyo3::prelude::*;
|
|
8
10
|
use pyo3::types::{PyString, PyType};
|
|
@@ -44,6 +46,35 @@ impl SheetsEnum {
|
|
|
44
46
|
SheetsEnum::None => Err(Error::WorkbookClosed),
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
fn worksheet_merge_cells(
|
|
51
|
+
&mut self,
|
|
52
|
+
name: &str,
|
|
53
|
+
) -> Result<Option<Vec<calamine::Dimensions>>, Error> {
|
|
54
|
+
match self {
|
|
55
|
+
SheetsEnum::File(f) => match f {
|
|
56
|
+
Sheets::Xls(xls_f) => Ok(xls_f.worksheet_merge_cells(name)),
|
|
57
|
+
Sheets::Xlsx(xlsx_f) => xlsx_f
|
|
58
|
+
.worksheet_merge_cells(name)
|
|
59
|
+
.transpose()
|
|
60
|
+
.map_err(CalamineCrateError::Xlsx)
|
|
61
|
+
.map_err(Error::Calamine)
|
|
62
|
+
.map(|inner| inner.or(Some(Vec::new()))),
|
|
63
|
+
_ => Ok(None),
|
|
64
|
+
},
|
|
65
|
+
SheetsEnum::FileLike(f) => match f {
|
|
66
|
+
Sheets::Xls(xls_f) => Ok(xls_f.worksheet_merge_cells(name)),
|
|
67
|
+
Sheets::Xlsx(xlsx_f) => xlsx_f
|
|
68
|
+
.worksheet_merge_cells(name)
|
|
69
|
+
.transpose()
|
|
70
|
+
.map_err(CalamineCrateError::Xlsx)
|
|
71
|
+
.map_err(Error::Calamine)
|
|
72
|
+
.map(|inner| inner.or(Some(Vec::new()))),
|
|
73
|
+
_ => Ok(None),
|
|
74
|
+
},
|
|
75
|
+
SheetsEnum::None => Err(Error::WorkbookClosed),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
47
78
|
}
|
|
48
79
|
|
|
49
80
|
#[pyclass]
|
|
@@ -61,7 +92,7 @@ pub struct CalamineWorkbook {
|
|
|
61
92
|
impl CalamineWorkbook {
|
|
62
93
|
fn __repr__(&self) -> PyResult<String> {
|
|
63
94
|
match &self.path {
|
|
64
|
-
Some(path) => Ok(format!("CalamineWorkbook(path='{}')"
|
|
95
|
+
Some(path) => Ok(format!("CalamineWorkbook(path='{path}')")),
|
|
65
96
|
None => Ok("CalamineWorkbook(path='bytes')".to_string()),
|
|
66
97
|
}
|
|
67
98
|
}
|
|
@@ -71,7 +102,7 @@ impl CalamineWorkbook {
|
|
|
71
102
|
fn py_from_object(
|
|
72
103
|
_cls: &Bound<'_, PyType>,
|
|
73
104
|
py: Python<'_>,
|
|
74
|
-
path_or_filelike:
|
|
105
|
+
path_or_filelike: Py<PyAny>,
|
|
75
106
|
) -> PyResult<Self> {
|
|
76
107
|
Self::from_object(py, path_or_filelike)
|
|
77
108
|
}
|
|
@@ -81,22 +112,22 @@ impl CalamineWorkbook {
|
|
|
81
112
|
fn py_from_filelike(
|
|
82
113
|
_cls: &Bound<'_, PyType>,
|
|
83
114
|
py: Python<'_>,
|
|
84
|
-
filelike:
|
|
115
|
+
filelike: Py<PyAny>,
|
|
85
116
|
) -> PyResult<Self> {
|
|
86
|
-
py.
|
|
117
|
+
py.detach(|| Self::from_filelike(filelike))
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
#[classmethod]
|
|
90
121
|
#[pyo3(name = "from_path")]
|
|
91
|
-
fn py_from_path(_cls: &Bound<'_, PyType>, py: Python<'_>, path:
|
|
122
|
+
fn py_from_path(_cls: &Bound<'_, PyType>, py: Python<'_>, path: Py<PyAny>) -> PyResult<Self> {
|
|
92
123
|
if let Ok(string_ref) = path.downcast_bound::<PyString>(py) {
|
|
93
124
|
let path = string_ref.to_string_lossy().to_string();
|
|
94
|
-
return py.
|
|
125
|
+
return py.detach(|| Self::from_path(&path));
|
|
95
126
|
}
|
|
96
127
|
|
|
97
128
|
if let Ok(string_ref) = path.extract::<PathBuf>(py) {
|
|
98
129
|
let path = string_ref.to_string_lossy().to_string();
|
|
99
|
-
return py.
|
|
130
|
+
return py.detach(|| Self::from_path(&path));
|
|
100
131
|
}
|
|
101
132
|
|
|
102
133
|
Err(PyTypeError::new_err(""))
|
|
@@ -104,12 +135,12 @@ impl CalamineWorkbook {
|
|
|
104
135
|
|
|
105
136
|
#[pyo3(name = "get_sheet_by_name")]
|
|
106
137
|
fn py_get_sheet_by_name(&mut self, py: Python<'_>, name: &str) -> PyResult<CalamineSheet> {
|
|
107
|
-
py.
|
|
138
|
+
py.detach(|| self.get_sheet_by_name(name))
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
#[pyo3(name = "get_sheet_by_index")]
|
|
111
142
|
fn py_get_sheet_by_index(&mut self, py: Python<'_>, index: usize) -> PyResult<CalamineSheet> {
|
|
112
|
-
py.
|
|
143
|
+
py.detach(|| self.get_sheet_by_index(index))
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
fn close(&mut self) -> PyResult<()> {
|
|
@@ -129,30 +160,30 @@ impl CalamineWorkbook {
|
|
|
129
160
|
|
|
130
161
|
fn __exit__(
|
|
131
162
|
&mut self,
|
|
132
|
-
_exc_type:
|
|
133
|
-
_exc_value:
|
|
134
|
-
_traceback:
|
|
163
|
+
_exc_type: Py<PyAny>,
|
|
164
|
+
_exc_value: Py<PyAny>,
|
|
165
|
+
_traceback: Py<PyAny>,
|
|
135
166
|
) -> PyResult<()> {
|
|
136
167
|
self.close()
|
|
137
168
|
}
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
impl CalamineWorkbook {
|
|
141
|
-
pub fn from_object(py: Python<'_>, path_or_filelike:
|
|
172
|
+
pub fn from_object(py: Python<'_>, path_or_filelike: Py<PyAny>) -> PyResult<Self> {
|
|
142
173
|
if let Ok(string_ref) = path_or_filelike.downcast_bound::<PyString>(py) {
|
|
143
174
|
let path = string_ref.to_string_lossy().to_string();
|
|
144
|
-
return py.
|
|
175
|
+
return py.detach(|| Self::from_path(&path));
|
|
145
176
|
}
|
|
146
177
|
|
|
147
178
|
if let Ok(string_ref) = path_or_filelike.extract::<PathBuf>(py) {
|
|
148
179
|
let path = string_ref.to_string_lossy().to_string();
|
|
149
|
-
return py.
|
|
180
|
+
return py.detach(|| Self::from_path(&path));
|
|
150
181
|
}
|
|
151
182
|
|
|
152
|
-
py.
|
|
183
|
+
py.detach(|| Self::from_filelike(path_or_filelike))
|
|
153
184
|
}
|
|
154
185
|
|
|
155
|
-
pub fn from_filelike(filelike:
|
|
186
|
+
pub fn from_filelike(filelike: Py<PyAny>) -> PyResult<Self> {
|
|
156
187
|
let mut buf = vec![];
|
|
157
188
|
PyFileLikeObject::with_requirements(filelike, true, false, true, false)?
|
|
158
189
|
.read_to_end(&mut buf)?;
|
|
@@ -192,14 +223,19 @@ impl CalamineWorkbook {
|
|
|
192
223
|
|
|
193
224
|
fn get_sheet_by_name(&mut self, name: &str) -> PyResult<CalamineSheet> {
|
|
194
225
|
let range = self.sheets.worksheet_range(name).map_err(err_to_py)?;
|
|
195
|
-
|
|
226
|
+
let merge_cells_range = self.sheets.worksheet_merge_cells(name).map_err(err_to_py)?;
|
|
227
|
+
Ok(CalamineSheet::new(
|
|
228
|
+
name.to_owned(),
|
|
229
|
+
range,
|
|
230
|
+
merge_cells_range,
|
|
231
|
+
))
|
|
196
232
|
}
|
|
197
233
|
|
|
198
234
|
fn get_sheet_by_index(&mut self, index: usize) -> PyResult<CalamineSheet> {
|
|
199
235
|
let name = self
|
|
200
236
|
.sheet_names
|
|
201
237
|
.get(index)
|
|
202
|
-
.ok_or_else(|| WorksheetNotFound::new_err(format!("Worksheet '{}' not found"
|
|
238
|
+
.ok_or_else(|| WorksheetNotFound::new_err(format!("Worksheet '{index}' not found")))?
|
|
203
239
|
.to_string();
|
|
204
240
|
self.get_sheet_by_name(&name)
|
|
205
241
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -15,7 +15,7 @@ PATH = Path(__file__).parent / "data"
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_ods_read():
|
|
18
|
-
names = ["Sheet1", "Sheet2"]
|
|
18
|
+
names = ["Sheet1", "Sheet2", "Merged Cells"]
|
|
19
19
|
data = [
|
|
20
20
|
["", "", "", "", "", "", "", "", "", ""],
|
|
21
21
|
[
|
|
@@ -59,9 +59,11 @@ def test_ods_read():
|
|
|
59
59
|
assert [] == reader.get_sheet_by_index(1).to_python()
|
|
60
60
|
assert [] == reader.get_sheet_by_index(1).to_python(skip_empty_area=False)
|
|
61
61
|
|
|
62
|
+
assert None is reader.get_sheet_by_name("Merged Cells").merged_cell_ranges
|
|
63
|
+
|
|
62
64
|
|
|
63
65
|
def test_xls_read():
|
|
64
|
-
names = ["Sheet1", "Sheet2"]
|
|
66
|
+
names = ["Sheet1", "Sheet2", "Merged Cells"]
|
|
65
67
|
data = [
|
|
66
68
|
["", "", "", "", "", "", "", "", "", ""],
|
|
67
69
|
[
|
|
@@ -101,9 +103,13 @@ def test_xls_read():
|
|
|
101
103
|
assert [] == reader.get_sheet_by_index(1).to_python()
|
|
102
104
|
assert [] == reader.get_sheet_by_index(1).to_python(skip_empty_area=False)
|
|
103
105
|
|
|
106
|
+
assert [((0, 0), (1, 0)), ((0, 1), (1, 1))] == reader.get_sheet_by_name(
|
|
107
|
+
"Merged Cells"
|
|
108
|
+
).merged_cell_ranges
|
|
109
|
+
|
|
104
110
|
|
|
105
111
|
def test_xlsb_read():
|
|
106
|
-
names = ["Sheet1", "Sheet2", "Sheet3"]
|
|
112
|
+
names = ["Sheet1", "Sheet2", "Sheet3", "Merged Cells"]
|
|
107
113
|
data = [
|
|
108
114
|
["", "", "", "", "", "", "", "", "", ""],
|
|
109
115
|
[
|
|
@@ -143,9 +149,11 @@ def test_xlsb_read():
|
|
|
143
149
|
assert [] == reader.get_sheet_by_index(1).to_python()
|
|
144
150
|
assert [] == reader.get_sheet_by_index(1).to_python(skip_empty_area=False)
|
|
145
151
|
|
|
152
|
+
assert None is reader.get_sheet_by_name("Merged Cells").merged_cell_ranges
|
|
153
|
+
|
|
146
154
|
|
|
147
155
|
def test_xlsx_read():
|
|
148
|
-
names = ["Sheet1", "Sheet2", "Sheet3"]
|
|
156
|
+
names = ["Sheet1", "Sheet2", "Sheet3", "Merged Cells"]
|
|
149
157
|
data = [
|
|
150
158
|
["", "", "", "", "", "", "", "", "", ""],
|
|
151
159
|
[
|
|
@@ -185,9 +193,13 @@ def test_xlsx_read():
|
|
|
185
193
|
assert [] == reader.get_sheet_by_index(1).to_python()
|
|
186
194
|
assert [] == reader.get_sheet_by_index(1).to_python(skip_empty_area=False)
|
|
187
195
|
|
|
196
|
+
assert [((0, 0), (1, 0)), ((0, 1), (1, 1))] == reader.get_sheet_by_name(
|
|
197
|
+
"Merged Cells"
|
|
198
|
+
).merged_cell_ranges
|
|
199
|
+
|
|
188
200
|
|
|
189
201
|
def test_xlsx_iter_rows():
|
|
190
|
-
names = ["Sheet1", "Sheet2", "Sheet3"]
|
|
202
|
+
names = ["Sheet1", "Sheet2", "Sheet3", "Merged Cells"]
|
|
191
203
|
data = [
|
|
192
204
|
["", "", "", "", "", "", "", "", "", ""],
|
|
193
205
|
[
|
|
@@ -363,3 +375,10 @@ def test_close_workbook_double():
|
|
|
363
375
|
|
|
364
376
|
with pytest.raises(WorkbookClosed):
|
|
365
377
|
reader.close()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def test_datetime_overflow():
|
|
381
|
+
reader = CalamineWorkbook.from_path(PATH / "issue139.xlsx")
|
|
382
|
+
|
|
383
|
+
# 2994626.0 = 10099-01-01
|
|
384
|
+
assert reader.get_sheet_by_index(0).to_python() == [["date"], [2994626.0]]
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from importlib.metadata import version
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
|
|
4
3
|
import pytest
|
|
5
|
-
from packaging.version import Version, parse
|
|
6
4
|
|
|
7
5
|
try:
|
|
8
6
|
import pandas as pd
|
|
@@ -14,17 +12,6 @@ except ImportError:
|
|
|
14
12
|
PATH = Path(__file__).parent / "data"
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
@pytest.fixture(scope="module", autouse=True)
|
|
18
|
-
def pandas_monkeypatch():
|
|
19
|
-
if parse(version("pandas")) >= Version("2.2.0"):
|
|
20
|
-
yield
|
|
21
|
-
else:
|
|
22
|
-
from python_calamine.pandas import pandas_monkeypatch
|
|
23
|
-
|
|
24
|
-
pandas_monkeypatch()
|
|
25
|
-
yield
|
|
26
|
-
|
|
27
|
-
|
|
28
15
|
@pytest.mark.skipif(not pd, reason="pandas is required")
|
|
29
16
|
def test_ods_pandas(expected_df_ods):
|
|
30
17
|
result = pd.read_excel(PATH / "base.ods", sheet_name="Sheet1", engine="calamine")
|
python_calamine-0.3.2/Cargo.toml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "python-calamine"
|
|
3
|
-
version = "0.3.2"
|
|
4
|
-
authors = ["Dmitriy <dimastbk@proton.me>"]
|
|
5
|
-
description = "Python binding for Rust's library for reading excel and odf file - calamine"
|
|
6
|
-
edition = "2021"
|
|
7
|
-
homepage = "https://github.com/dimastbk/python-calamine"
|
|
8
|
-
license = "MIT"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
repository = "https://github.com/dimastbk/python-calamine"
|
|
11
|
-
|
|
12
|
-
[lib]
|
|
13
|
-
name = "python_calamine"
|
|
14
|
-
crate-type = ["cdylib"]
|
|
15
|
-
|
|
16
|
-
[dependencies]
|
|
17
|
-
calamine = { git = "https://github.com/tafia/calamine.git", rev = "5cda5c734661f656bdfb7ac7a1b95e77bf89aa09", features = ["dates"] }
|
|
18
|
-
pyo3 = { version = "0.24.0", features = [
|
|
19
|
-
"extension-module",
|
|
20
|
-
"chrono",
|
|
21
|
-
"generate-import-lib",
|
|
22
|
-
] }
|
|
23
|
-
chrono = { version = "0.4.38", features = ["serde"] }
|
|
24
|
-
pyo3-file = "0.12.0"
|
|
25
|
-
|
|
26
|
-
[build-dependencies]
|
|
27
|
-
pyo3-build-config = "0.24.0"
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from datetime import date, datetime, time, timedelta
|
|
4
|
-
from importlib.metadata import version
|
|
5
|
-
from typing import TYPE_CHECKING, Union, cast
|
|
6
|
-
|
|
7
|
-
import pandas as pd
|
|
8
|
-
from packaging.version import Version, parse
|
|
9
|
-
from pandas._typing import Scalar
|
|
10
|
-
from pandas.compat._optional import import_optional_dependency
|
|
11
|
-
from pandas.core.shared_docs import _shared_docs
|
|
12
|
-
from pandas.io.excel import ExcelFile
|
|
13
|
-
from pandas.io.excel._base import ( # type:ignore[attr-defined] # missing in pandas-stubs
|
|
14
|
-
BaseExcelReader,
|
|
15
|
-
)
|
|
16
|
-
from pandas.util._decorators import ( # type:ignore[attr-defined] # missing in pandas-stubs
|
|
17
|
-
doc,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from pandas._typing import FilePath, ReadBuffer, StorageOptions
|
|
22
|
-
from python_calamine import CalamineSheet, CalamineWorkbook
|
|
23
|
-
|
|
24
|
-
_CellValueT = Union[int, float, str, bool, time, date, datetime, timedelta]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
PANDAS_VERSION = parse(version("pandas"))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class CalamineExcelReader(BaseExcelReader):
|
|
31
|
-
book: CalamineWorkbook
|
|
32
|
-
|
|
33
|
-
@doc(storage_options=_shared_docs["storage_options"])
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
filepath_or_buffer: FilePath | ReadBuffer[bytes],
|
|
37
|
-
storage_options: StorageOptions | None = None,
|
|
38
|
-
engine_kwargs: dict | None = None,
|
|
39
|
-
) -> None:
|
|
40
|
-
"""
|
|
41
|
-
Reader using calamine engine (xlsx/xls/xlsb/ods).
|
|
42
|
-
|
|
43
|
-
Parameters
|
|
44
|
-
----------
|
|
45
|
-
filepath_or_buffer : str, path to be parsed or
|
|
46
|
-
an open readable stream.
|
|
47
|
-
{storage_options}
|
|
48
|
-
engine_kwargs : dict, optional
|
|
49
|
-
Arbitrary keyword arguments passed to excel engine.
|
|
50
|
-
"""
|
|
51
|
-
import_optional_dependency("python_calamine")
|
|
52
|
-
if PANDAS_VERSION >= Version("2.2.0"):
|
|
53
|
-
raise ValueError("Pandas >= 2.2.0 has builtin support of calamine")
|
|
54
|
-
elif PANDAS_VERSION >= Version("2.1.0"):
|
|
55
|
-
super().__init__(
|
|
56
|
-
filepath_or_buffer,
|
|
57
|
-
storage_options=storage_options,
|
|
58
|
-
engine_kwargs=engine_kwargs,
|
|
59
|
-
)
|
|
60
|
-
elif PANDAS_VERSION >= Version("2.0.0"):
|
|
61
|
-
super().__init__(
|
|
62
|
-
filepath_or_buffer,
|
|
63
|
-
storage_options=storage_options,
|
|
64
|
-
)
|
|
65
|
-
else:
|
|
66
|
-
raise ValueError("Pandas >= 2 is only supported")
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def _workbook_class(self) -> type[CalamineWorkbook]:
|
|
70
|
-
from python_calamine import CalamineWorkbook
|
|
71
|
-
|
|
72
|
-
return CalamineWorkbook
|
|
73
|
-
|
|
74
|
-
def load_workbook(
|
|
75
|
-
self,
|
|
76
|
-
filepath_or_buffer: FilePath | ReadBuffer[bytes],
|
|
77
|
-
engine_kwargs: dict | None = None,
|
|
78
|
-
) -> CalamineWorkbook:
|
|
79
|
-
from python_calamine import load_workbook
|
|
80
|
-
|
|
81
|
-
return load_workbook(filepath_or_buffer, **(engine_kwargs or {}))
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def sheet_names(self) -> list[str]:
|
|
85
|
-
from python_calamine import SheetTypeEnum
|
|
86
|
-
|
|
87
|
-
return [
|
|
88
|
-
sheet.name
|
|
89
|
-
for sheet in self.book.sheets_metadata
|
|
90
|
-
if sheet.typ == SheetTypeEnum.WorkSheet
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
def get_sheet_by_name(self, name: str) -> CalamineSheet:
|
|
94
|
-
self.raise_if_bad_sheet_by_name(name)
|
|
95
|
-
return self.book.get_sheet_by_name(name)
|
|
96
|
-
|
|
97
|
-
def get_sheet_by_index(self, index: int) -> CalamineSheet:
|
|
98
|
-
self.raise_if_bad_sheet_by_index(index)
|
|
99
|
-
return self.book.get_sheet_by_index(index)
|
|
100
|
-
|
|
101
|
-
def get_sheet_data(
|
|
102
|
-
self, sheet: CalamineSheet, file_rows_needed: int | None = None
|
|
103
|
-
) -> list[list[Scalar]]:
|
|
104
|
-
def _convert_cell(value: _CellValueT) -> Scalar:
|
|
105
|
-
if isinstance(value, float):
|
|
106
|
-
val = int(value)
|
|
107
|
-
if val == value:
|
|
108
|
-
return val
|
|
109
|
-
else:
|
|
110
|
-
return value
|
|
111
|
-
elif isinstance(value, date):
|
|
112
|
-
return pd.Timestamp(value)
|
|
113
|
-
elif isinstance(value, timedelta):
|
|
114
|
-
return pd.Timedelta(value)
|
|
115
|
-
elif isinstance(value, time):
|
|
116
|
-
# cast needed here because Scalar doesn't include datetime.time
|
|
117
|
-
return cast(Scalar, value)
|
|
118
|
-
|
|
119
|
-
return value
|
|
120
|
-
|
|
121
|
-
rows: list[list[_CellValueT]] = sheet.to_python(skip_empty_area=False)
|
|
122
|
-
data: list[list[Scalar]] = []
|
|
123
|
-
|
|
124
|
-
for row in rows:
|
|
125
|
-
data.append([_convert_cell(cell) for cell in row])
|
|
126
|
-
if file_rows_needed is not None and len(data) >= file_rows_needed:
|
|
127
|
-
break
|
|
128
|
-
|
|
129
|
-
return data
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def pandas_monkeypatch() -> None:
|
|
133
|
-
ExcelFile._engines = { # type:ignore[attr-defined] # missing in pandas-stubs
|
|
134
|
-
"calamine": CalamineExcelReader,
|
|
135
|
-
**ExcelFile._engines, # type:ignore[attr-defined] # missing in pandas-stubs
|
|
136
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|