xlwings-utils 25.0.0__py3-none-any.whl → 25.0.0.post2__py3-none-any.whl
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.
Potentially problematic release.
This version of xlwings-utils might be problematic. Click here for more details.
- xlwings_utils/xlwings_utils.py +265 -20
- {xlwings_utils-25.0.0.dist-info → xlwings_utils-25.0.0.post2.dist-info}/METADATA +49 -1
- xlwings_utils-25.0.0.post2.dist-info/RECORD +6 -0
- xlwings_utils-25.0.0.dist-info/RECORD +0 -6
- {xlwings_utils-25.0.0.dist-info → xlwings_utils-25.0.0.post2.dist-info}/WHEEL +0 -0
- {xlwings_utils-25.0.0.dist-info → xlwings_utils-25.0.0.post2.dist-info}/top_level.txt +0 -0
xlwings_utils/xlwings_utils.py
CHANGED
|
@@ -12,6 +12,7 @@ import dropbox
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
|
+
import math
|
|
15
16
|
|
|
16
17
|
dbx = None
|
|
17
18
|
Pythonista = sys.platform == "ios"
|
|
@@ -287,6 +288,17 @@ class block:
|
|
|
287
288
|
if number_of_columns is not None:
|
|
288
289
|
self.number_of_columns = number_of_columns
|
|
289
290
|
|
|
291
|
+
@classmethod
|
|
292
|
+
def from_xlrd_sheet(cls,sheet, number_of_rows=None, number_of_columns=None):
|
|
293
|
+
v = [sheet.row_values(row_idx)[0 : sheet.ncols] for row_idx in range(0, sheet.nrows)]
|
|
294
|
+
return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def from_dataframe(cls,df, number_of_rows=None, number_of_columns=None):
|
|
299
|
+
v=df.values.tolist()
|
|
300
|
+
return cls(v, number_of_rows=number_of_rows, number_of_columns=number_of_columns)
|
|
301
|
+
|
|
290
302
|
@property
|
|
291
303
|
def value(self):
|
|
292
304
|
return [[self.dict.get((row, column)) for column in range(1, self.number_of_columns + 1)] for row in range(1, self.number_of_rows + 1)]
|
|
@@ -306,7 +318,7 @@ class block:
|
|
|
306
318
|
|
|
307
319
|
for row, row_contents in enumerate(value, 1):
|
|
308
320
|
for column, item in enumerate(row_contents, 1):
|
|
309
|
-
if item
|
|
321
|
+
if item and not (isinstance(item,float) and math.isnan(item)):
|
|
310
322
|
self.dict[row, column] = item
|
|
311
323
|
self._number_of_columns = max(self.number_of_columns, column)
|
|
312
324
|
|
|
@@ -327,6 +339,12 @@ class block:
|
|
|
327
339
|
return self.dict.get((row, column))
|
|
328
340
|
|
|
329
341
|
def minimized(self):
|
|
342
|
+
"""
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
minimized block : block
|
|
346
|
+
uses highest_used_row_number and highest_used_column_number to minimize the block
|
|
347
|
+
"""
|
|
330
348
|
return block(self, number_of_rows=self.highest_used_row_number, number_of_columns=self.highest_used_column_number)
|
|
331
349
|
|
|
332
350
|
@property
|
|
@@ -372,6 +390,236 @@ class block:
|
|
|
372
390
|
def __repr__(self):
|
|
373
391
|
return f"block({self.value})"
|
|
374
392
|
|
|
393
|
+
def _check_row(self, row, name):
|
|
394
|
+
if row < 1:
|
|
395
|
+
raise ValueError(f"{name}={row} < 1")
|
|
396
|
+
if row > self.number_of_rows:
|
|
397
|
+
raise ValueError(f"{name}={row} > number_of_rows={self.number_of_rows}")
|
|
398
|
+
|
|
399
|
+
def _check_column(self, column, name):
|
|
400
|
+
if column < 1:
|
|
401
|
+
raise ValueError(f"{name}={column} < 1")
|
|
402
|
+
if column > self.number_of_columns:
|
|
403
|
+
raise ValueError(f"{name}={column} > number_of_columns={self.number_of_columns}")
|
|
404
|
+
|
|
405
|
+
def vlookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
|
|
406
|
+
"""
|
|
407
|
+
searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
s : any
|
|
412
|
+
value to seach for
|
|
413
|
+
|
|
414
|
+
row_from : int
|
|
415
|
+
row to start search (default 1)
|
|
416
|
+
|
|
417
|
+
should be between 1 and number_of_rows
|
|
418
|
+
|
|
419
|
+
row_to : int
|
|
420
|
+
row to end search (default number_of_rows)
|
|
421
|
+
|
|
422
|
+
should be between 1 and number_of_rows
|
|
423
|
+
|
|
424
|
+
column1 : int
|
|
425
|
+
column to search in (default 1)
|
|
426
|
+
|
|
427
|
+
should be between 1 and number_of_columns
|
|
428
|
+
|
|
429
|
+
column2 : int
|
|
430
|
+
column to return looked up value from (default column1 + 1)
|
|
431
|
+
|
|
432
|
+
should be between 1 and number_of_columns
|
|
433
|
+
|
|
434
|
+
Returns
|
|
435
|
+
-------
|
|
436
|
+
block[found row number, column2] : any
|
|
437
|
+
|
|
438
|
+
Note
|
|
439
|
+
----
|
|
440
|
+
If s is not found, a ValueError is raised
|
|
441
|
+
"""
|
|
442
|
+
if column2 is None:
|
|
443
|
+
column2 = column1 + 1
|
|
444
|
+
self._check_column(column2, "column2")
|
|
445
|
+
row = self.lookup_row(s, row_from=row_from, row_to=row_to, column1=column1)
|
|
446
|
+
return self[row, column2]
|
|
447
|
+
|
|
448
|
+
def lookup_row(self, s, *, row_from=1, row_to=None, column1=1):
|
|
449
|
+
"""
|
|
450
|
+
searches in column1 for row between row_from and row_to for s and returns that row number
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
s : any
|
|
455
|
+
value to seach for
|
|
456
|
+
|
|
457
|
+
row_from : int
|
|
458
|
+
row to start search (default 1)
|
|
459
|
+
|
|
460
|
+
should be between 1 and number_of_rows
|
|
461
|
+
|
|
462
|
+
row_to : int
|
|
463
|
+
row to end search (default number_of_rows)
|
|
464
|
+
|
|
465
|
+
should be between 1 and number_of_rows
|
|
466
|
+
|
|
467
|
+
column1 : int
|
|
468
|
+
column to search in (default 1)
|
|
469
|
+
|
|
470
|
+
should be between 1 and number_of_columns
|
|
471
|
+
|
|
472
|
+
column2 : int
|
|
473
|
+
column to return looked up value from (default column1 + 1)
|
|
474
|
+
|
|
475
|
+
Returns
|
|
476
|
+
-------
|
|
477
|
+
row number where block[row nunber, column1] == s : int
|
|
478
|
+
|
|
479
|
+
Note
|
|
480
|
+
----
|
|
481
|
+
If s is not found, a ValueError is raised
|
|
482
|
+
"""
|
|
483
|
+
if row_to is None:
|
|
484
|
+
row_to = self.highest_used_row_number
|
|
485
|
+
self._check_row(row_from, "row_from")
|
|
486
|
+
self._check_row(row_to, "row_to")
|
|
487
|
+
self._check_column(column1, "column1")
|
|
488
|
+
|
|
489
|
+
for row in range(row_from, row_to + 1):
|
|
490
|
+
if self[row, column1] == s:
|
|
491
|
+
return row
|
|
492
|
+
raise ValueError(f"{s} not found")
|
|
493
|
+
|
|
494
|
+
def hlookup(self, s, *, column_from=1, column_to=None, row1=1, row2=None):
|
|
495
|
+
"""
|
|
496
|
+
searches in row1 for column between column_from and column_to for s and returns the value found at (that column, row2)
|
|
497
|
+
|
|
498
|
+
Parameters
|
|
499
|
+
----------
|
|
500
|
+
s : any
|
|
501
|
+
value to seach for
|
|
502
|
+
|
|
503
|
+
column_from : int
|
|
504
|
+
column to start search (default 1)
|
|
505
|
+
|
|
506
|
+
should be between 1 and number_of_columns
|
|
507
|
+
|
|
508
|
+
column_to : int
|
|
509
|
+
column to end search (default number_of_columns)
|
|
510
|
+
|
|
511
|
+
should be between 1 and number_of_columns
|
|
512
|
+
|
|
513
|
+
row1 : int
|
|
514
|
+
row to search in (default 1)
|
|
515
|
+
|
|
516
|
+
should be between 1 and number_of_rows
|
|
517
|
+
|
|
518
|
+
row2 : int
|
|
519
|
+
row to return looked up value from (default row1 + 1)
|
|
520
|
+
|
|
521
|
+
should be between 1 and number_of_rows
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
block[row, found column, row2] : any
|
|
526
|
+
|
|
527
|
+
Note
|
|
528
|
+
----
|
|
529
|
+
If s is not found, a ValueError is raised
|
|
530
|
+
"""
|
|
531
|
+
if column2 is None:
|
|
532
|
+
row2 = row1 + 1
|
|
533
|
+
self._check_row(row2, "row2")
|
|
534
|
+
column = self.lookup_column(s, column_from=column_from, column_to=column_to, row1=row1)
|
|
535
|
+
return self[row2, column]
|
|
536
|
+
|
|
537
|
+
def lookup_column(self, s, *, column_from=1, column_to=None, row1=1):
|
|
538
|
+
"""
|
|
539
|
+
searches in row1 for column between column_from and column_to for s and returns that column number
|
|
540
|
+
|
|
541
|
+
Parameters
|
|
542
|
+
----------
|
|
543
|
+
s : any
|
|
544
|
+
value to seach for
|
|
545
|
+
|
|
546
|
+
column_from : int
|
|
547
|
+
column to start search (default 1)
|
|
548
|
+
|
|
549
|
+
should be between 1 and number_of_columns
|
|
550
|
+
|
|
551
|
+
column_to : int
|
|
552
|
+
column to end search (default number_of_columns)
|
|
553
|
+
|
|
554
|
+
should be between 1 and number_of_columns
|
|
555
|
+
|
|
556
|
+
row1 : int
|
|
557
|
+
row to search in (default 1)
|
|
558
|
+
|
|
559
|
+
should be between 1 and number_of_rows
|
|
560
|
+
|
|
561
|
+
row2 : int
|
|
562
|
+
row to return looked up value from (default row1 + 1)
|
|
563
|
+
|
|
564
|
+
Returns
|
|
565
|
+
-------
|
|
566
|
+
column number where block[row1, column number] == s : int
|
|
567
|
+
|
|
568
|
+
Note
|
|
569
|
+
----
|
|
570
|
+
If s is not found, a ValueError is raised
|
|
571
|
+
"""
|
|
572
|
+
if column_to is None:
|
|
573
|
+
column_to = self.highest_used_column_number
|
|
574
|
+
self._check_column(column_from, "column_from")
|
|
575
|
+
self._check_column(column_to, "column_to")
|
|
576
|
+
self._check_row(row1, "row1")
|
|
577
|
+
|
|
578
|
+
for column in range(column_from, column_to + 1):
|
|
579
|
+
if self[row1, column] == s:
|
|
580
|
+
return column
|
|
581
|
+
raise ValueError(f"{s} not found")
|
|
582
|
+
|
|
583
|
+
def lookup(self, s, *, row_from=1, row_to=None, column1=1, column2=None):
|
|
584
|
+
"""
|
|
585
|
+
searches in column1 for row between row_from and row_to for s and returns the value found at (that row, column2)
|
|
586
|
+
|
|
587
|
+
Parameters
|
|
588
|
+
----------
|
|
589
|
+
s : any
|
|
590
|
+
value to seach for
|
|
591
|
+
|
|
592
|
+
row_from : int
|
|
593
|
+
row to start search (default 1)
|
|
594
|
+
|
|
595
|
+
should be between 1 and number_of_rows
|
|
596
|
+
|
|
597
|
+
row_to : int
|
|
598
|
+
row to end search (default number_of_rows)
|
|
599
|
+
|
|
600
|
+
should be between 1 and number_of_rows
|
|
601
|
+
|
|
602
|
+
column1 : int
|
|
603
|
+
column to search in (default 1)
|
|
604
|
+
|
|
605
|
+
should be between 1 and number_of_columns
|
|
606
|
+
|
|
607
|
+
column2 : int
|
|
608
|
+
column to return looked up value from (default column1 + 1)
|
|
609
|
+
|
|
610
|
+
should be between 1 and number_of_columns
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
block[found row number, column2] : any
|
|
615
|
+
|
|
616
|
+
Note
|
|
617
|
+
----
|
|
618
|
+
If s is not found, a ValueError is raised
|
|
619
|
+
|
|
620
|
+
This is exactly the same as vlookup.
|
|
621
|
+
"""
|
|
622
|
+
return self.vlookup(s, row_from=row_from, row_to=row_to, column1=column1, column2=column2)
|
|
375
623
|
|
|
376
624
|
|
|
377
625
|
class _capture:
|
|
@@ -396,7 +644,7 @@ class _capture:
|
|
|
396
644
|
def __init__(self):
|
|
397
645
|
self.stdout = sys.stdout
|
|
398
646
|
self._include_print = False
|
|
399
|
-
self._buffer=[]
|
|
647
|
+
self._buffer = []
|
|
400
648
|
|
|
401
649
|
def __enter__(self):
|
|
402
650
|
sys.stdout = self
|
|
@@ -413,50 +661,47 @@ class _capture:
|
|
|
413
661
|
if self._include_print:
|
|
414
662
|
self.stdout.flush()
|
|
415
663
|
self._buffer.append("\n")
|
|
416
|
-
|
|
417
664
|
|
|
418
665
|
@property
|
|
419
666
|
def value(self):
|
|
420
|
-
result=self.value_keep
|
|
667
|
+
result = self.value_keep
|
|
421
668
|
self.clear()
|
|
422
669
|
return result
|
|
423
|
-
|
|
424
|
-
|
|
670
|
+
|
|
425
671
|
@property
|
|
426
672
|
def value_keep(self):
|
|
427
|
-
result= [[line] for line in self.str_keep.splitlines()]
|
|
673
|
+
result = [[line] for line in self.str_keep.splitlines()]
|
|
428
674
|
return result
|
|
429
|
-
|
|
675
|
+
|
|
430
676
|
@property
|
|
431
677
|
def str(self):
|
|
432
678
|
result = self.str_keep
|
|
433
679
|
self._buffer.clear()
|
|
434
|
-
return result
|
|
435
|
-
|
|
680
|
+
return result
|
|
681
|
+
|
|
436
682
|
@property
|
|
437
683
|
def str_keep(self):
|
|
438
684
|
result = "".join(self._buffer)
|
|
439
|
-
return result
|
|
440
|
-
|
|
685
|
+
return result
|
|
686
|
+
|
|
441
687
|
def clear(self):
|
|
442
688
|
self._buffer.clear()
|
|
443
|
-
|
|
444
|
-
|
|
689
|
+
|
|
445
690
|
@property
|
|
446
691
|
def include_print(self):
|
|
447
692
|
return self._include_print
|
|
448
|
-
|
|
693
|
+
|
|
449
694
|
@include_print.setter
|
|
450
695
|
def include_print(self, value):
|
|
451
|
-
self._include_print=value
|
|
452
|
-
|
|
696
|
+
self._include_print = value
|
|
453
697
|
|
|
454
698
|
|
|
455
699
|
def reset():
|
|
456
|
-
capture.include_print=False
|
|
700
|
+
capture.include_print = False
|
|
457
701
|
capture.clear()
|
|
458
|
-
|
|
459
|
-
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
capture = _capture()
|
|
460
705
|
|
|
461
706
|
|
|
462
707
|
if __name__ == "__main__":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xlwings_utils
|
|
3
|
-
Version: 25.0.0
|
|
3
|
+
Version: 25.0.0.post2
|
|
4
4
|
Summary: xlwings_utils
|
|
5
5
|
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/salabim/xlwings_utils
|
|
@@ -119,6 +119,54 @@ sheet.range(10,1).value = this_block.minimized().value
|
|
|
119
119
|
|
|
120
120
|
In this case, only the really processed rows are copied to the sheet.
|
|
121
121
|
|
|
122
|
+
With blocks, it is easy to use a sheet as an input for a project / scenario.
|
|
123
|
+
|
|
124
|
+
Like, something like
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Of course we could access the various input fields with absolute ranges, but if something
|
|
128
|
+
changes later (like adding a row), all references have to be updated.
|
|
129
|
+
|
|
130
|
+
If we read the project sheet (partly) into a block, lookup methods are available to access 'fields' easily and future proof.
|
|
131
|
+
|
|
132
|
+
Let's see how this works with the above sheet. The block (bl) looks like
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
| 1 2 3 4 5
|
|
136
|
+
--+-------------------------------------------------------------------------------
|
|
137
|
+
1 | Project Factory1
|
|
138
|
+
2 | Name Mega1
|
|
139
|
+
3 | Start date 2025-05-17
|
|
140
|
+
4 | End date 2026-02-01
|
|
141
|
+
5 |
|
|
142
|
+
6 | Parts Width Length Height Weight
|
|
143
|
+
7 | A 10 5 5 100
|
|
144
|
+
8 | B 11 5 8 102
|
|
145
|
+
9 | C 12 2 3 91
|
|
146
|
+
10|
|
|
147
|
+
|
|
148
|
+
Now we can do
|
|
149
|
+
project = bl.lookup("Project")
|
|
150
|
+
name = bl.lookup("Start date")
|
|
151
|
+
start_date = bl.lookup("Start date")
|
|
152
|
+
end_date = bl.lookup("End date")
|
|
153
|
+
row1 = bl.lookup_row("Parts")
|
|
154
|
+
parts=[]
|
|
155
|
+
for row2 in range(row1 + 1, bl.highest_used_row_number + 1):
|
|
156
|
+
if not (part_name := bl.hlookup("Part",row1=row1, row2=row2)):
|
|
157
|
+
# stop when reach a 'blank' part_name
|
|
158
|
+
break
|
|
159
|
+
width = bl.hlookup("Width",row1=row1, row2=row2)
|
|
160
|
+
length = bl.hlookup("Length",row1=row1, row2=row2)
|
|
161
|
+
height = bl.hlookup("HeightL",row1=row1, row2=row2)
|
|
162
|
+
weight = bl.hlookup("Weight",row1=row1, row2=row2)
|
|
163
|
+
parts.append(Part(part_name, width, length, height, weight))
|
|
164
|
+
|
|
165
|
+
You see lookup, which is vertical lookup, which just scans column 1 for the given label and then returns the corresponding value from column 2.
|
|
166
|
+
|
|
167
|
+
Then, there's lookup_row, which also scans column1 for the given label (Parts), but returns the corresponding row (6). Store it in row1.
|
|
168
|
+
And then we just read the following rows (with hlookup) and access the required values.
|
|
169
|
+
|
|
122
170
|
## Capture stdout support
|
|
123
171
|
|
|
124
172
|
The module has support for capturing stdout and -later- using showing the captured output on a sheet.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
|
|
2
|
+
xlwings_utils/xlwings_utils.py,sha256=bqy2SkFBf2GvHPLcXu1YpyLESiIHkT3rpSemruy5twc,20792
|
|
3
|
+
xlwings_utils-25.0.0.post2.dist-info/METADATA,sha256=PJyv0aEsxLn5v3V8kVtriKjl_2tjwjXXNogHjKNEMt8,8358
|
|
4
|
+
xlwings_utils-25.0.0.post2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
5
|
+
xlwings_utils-25.0.0.post2.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
|
|
6
|
+
xlwings_utils-25.0.0.post2.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
xlwings_utils/__init__.py,sha256=FdaRztevSu5akGL7KBUBRzqwLMRTdvVUuS2Kfp2f1Uc,68
|
|
2
|
-
xlwings_utils/xlwings_utils.py,sha256=NX8bnrKeG3znlI78F4nN0rMQoL-SCXHMIlIHyxxyMlY,12732
|
|
3
|
-
xlwings_utils-25.0.0.dist-info/METADATA,sha256=p4CcbZ-Ju0_F1vvr1ES_ZOhrCrDR1veBXNol4LH9ktE,6160
|
|
4
|
-
xlwings_utils-25.0.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
5
|
-
xlwings_utils-25.0.0.dist-info/top_level.txt,sha256=kf5SEv0gZiRObPhUoYcc1O_iX_wwTOPeUIYvzyYeAM4,14
|
|
6
|
-
xlwings_utils-25.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|